the provider automatically just creates one instance only from calling closure and reusing the instance, so the closure is only called once. If you want the provider to call closure for every injection, you can use addTransient method:
Don’t forget that it will throw an uncatchable Error if the provider is not registered yet. If you want to catch the error manually, just use tryInject instead:
class InjectedByInit {
var dependency: Dependency
init(dependency: Dependency? = nil) {
do {
self.dependency = dependency ?? try tryInject(for: Dependency.self)
} catch {
self.dependency = DefaultDependency()
}
}
}
Safe Injection
Sometimes you just don’t want your app to be throwing errors just because it’s failing in dependency injection. In those cases, just use @SafelyInjected attribute or injectIfProvided function. It will return nil if the injection fails:
class InjectedByPropertyWrapper {
@SafelyInjected var dependency: Dependency?
...
...
}
class InjectedByInit {
var dependency: Dependency
init(dependency: Dependency? = injectIfProvided(for: Dependency.self)) {
self.dependency = dependency
}
}
You can always give closure or auto closure to call if the injection fails:
class InjectedByInit {
var dependency: Dependency
init(dependency: Dependency? = inject(Dependency.self, ifFailUse: SomeDependency())) {
self.dependency = dependency
}
}
Singleton Provider
The simplest injection Provider is the Singleton provider. The provider just creates one instance, stores it, and reused the instance, so the closure is only called once. The instance will not be released until the Injector is released. It will be useful for shared instance dependencies:
Different from Singleton, Transient will not store the dependency at all, it will just recreate the dependency every time it’s needed. The closure will be stored strongly tho. It will be useful for services that store nothing:
This provider is a combination of singleton and transient providers. It will store the instance in a weak variable before returning it. Once the stored instance became nil, it will recreate a new instance for the next injection. The closure will be stored strongly tho. It will be useful for dependency that you want to use and share but released when not used anymore:
You can define a specific environment for a specific object that will live with that object and become the primary source of dependencies by using the Environment object:
In the code above, the myObjectInjected propertyWrapper will use the Environment as the primary source of the dependency. It will search in Injector.shared tho if the dependency is not provided by the Environment.
You can transfer the dependency providers from another object Environment to another, so it will use a similar Environment:
In the code above, someObject will have a new Environment that contains all of the myObject dependency providers, plus the one added later. It will populate the dependency from myObject Injected propertyWrapper too if it’s assigned manually.
class MyObject {
@Injected manual: ManualDependency
init() {
// this dependency will be transfered to another Environment created from this object
manualDependency = MyManualDependency()
}
}
Circular Dependency
Injected and SafelyInjected propertyWrapper will resolve dependency lazily, thus it will work even if you have a circular dependency. But it will rise a stack overflow error if you use inject function instead of init since it will resolve the dependency right away. Even tho circular dependency is not recommended, it will be better if you use propertyWrapper instead for injection to avoid this problem.
Multiple Types for one Provider
You can register multiple types for one provider if you need to:
You could have multiple Injector to provide different dependencies for the same type:
Injector.shared.addTransient(for: Dependency.self, Primary())
let secondaryInjector = Injector()
secondaryInjector.addTransient(for: Dependency.self, Secondary())
to use the other injector, switch it:
Injector.switchInjector(to: secondaryInjector)
to switch back is as easy as calling the void method:
Injector.switchToDefaultInjector()
Module Provider
If you have a modular project and want the individual module to inject everything manually by itself. You can use ModuleProvider protocol, and use it as a provider in the main module:
// this is in MyModule
class MyModuleInjector: ModuleProvider {
func provide(for injector: Injector) {
injector.addSingleton(for: Dependency.self, SomeDependency())
}
}
It will call provide(using:) with the given Injector. You can add as many ModuleProvider as you need, but if the Module provides the same Dependency for the same type of resolver, it will override the previous one with the new one.
AutoInjectMock
If you want to do a unit test and need some dependencies to be mocked, you can skip the injection in the test preparation and implement AutoInjectMock on your mock object like this:
class MyServiceMock: MyServiceProtocol, AutoInjectMock {
static var registeredTypes: [Any.Type] = [MyServiceProtocol.self]
..
..
}
Then in your unit test, you can use it like this:
serviceMock = MyServiceMock().injected()
// or
serviceMock = MyServiceMock().injected(using: customInjector)
Don’t forget, you should use a class instance for this to work because the injected instance will be different if you are using struct because of its nature.
Impose
Impose is a simple dependency injection library for Swift
Requirements
Only Swift Package Manager
Installation
Cocoapods
Impose is available through CocoaPods. To install it, simply add the following line to your Podfile:
Swift Package Manager from XCode
Swift Package Manager from Package.swift
Add as your target dependency in Package.swift
Use it in your target as
Impose
Author
Nayanda Haberty, hainayanda@outlook.com
License
Impose is available under the MIT license. See the LICENSE file for more info.
Basic Usage
Impose is very easy to use and straightforward, all you need to do is provide some provider for dependency:
and then use it in some of your classes using property wrapper or using global function
the provider is autoClosure type, so you can do something like this:
the provider automatically just creates one instance only from calling closure and reusing the instance, so the closure is only called once. If you want the provider to call closure for every injection, you can use
addTransient
method:Don’t forget that it will throw an uncatchable Error if the provider is not registered yet. If you want to catch the error manually, just use
tryInject
instead:Safe Injection
Sometimes you just don’t want your app to be throwing errors just because it’s failing in dependency injection. In those cases, just use
@SafelyInjected
attribute orinjectIfProvided
function. It will return nil if the injection fails:You can always give closure or auto closure to call if the injection fails:
Singleton Provider
The simplest injection Provider is the Singleton provider. The provider just creates one instance, stores it, and reused the instance, so the closure is only called once. The instance will not be released until the Injector is released. It will be useful for shared instance dependencies:
Transient Provider
Different from Singleton, Transient will not store the dependency at all, it will just recreate the dependency every time it’s needed. The closure will be stored strongly tho. It will be useful for services that store nothing:
Weak Provider
This provider is a combination of singleton and transient providers. It will store the instance in a weak variable before returning it. Once the stored instance became nil, it will recreate a new instance for the next injection. The closure will be stored strongly tho. It will be useful for dependency that you want to use and share but released when not used anymore:
Environment
You can define a specific environment for a specific object that will live with that object and become the primary source of dependencies by using the Environment object:
In the code above, the
myObject
Injected
propertyWrapper will use the Environment as the primary source of the dependency. It will search inInjector.shared
tho if the dependency is not provided by the Environment.You can transfer the dependency providers from another object Environment to another, so it will use a similar Environment:
In the code above, someObject will have a new Environment that contains all of the myObject dependency providers, plus the one added later. It will populate the dependency from myObject Injected propertyWrapper too if it’s assigned manually.
Circular Dependency
Injected
andSafelyInjected
propertyWrapper will resolve dependency lazily, thus it will work even if you have a circular dependency. But it will rise a stack overflow error if you use inject function instead of init since it will resolve the dependency right away. Even tho circular dependency is not recommended, it will be better if you use propertyWrapper instead for injection to avoid this problem.Multiple Types for one Provider
You can register multiple types for one provider if you need to:
or for transient:
or even for the environment:
Multiple Injectors
You could have multiple
Injector
to provide different dependencies for the same type:to use the other injector, switch it:
to switch back is as easy as calling the void method:
Module Provider
If you have a modular project and want the individual module to inject everything manually by itself. You can use
ModuleProvider
protocol, and use it as a provider in the main module:then let’s say in your
AppDelegate
:It will call
provide(using:)
with the givenInjector
. You can add as manyModuleProvider
as you need, but if the Module provides the same Dependency for the same type of resolver, it will override the previous one with the new one.AutoInjectMock
If you want to do a unit test and need some dependencies to be mocked, you can skip the injection in the test preparation and implement
AutoInjectMock
on your mock object like this:Then in your unit test, you can use it like this:
Don’t forget, you should use a class instance for this to work because the injected instance will be different if you are using struct because of its nature.
Contribute
You know how, just clone and do pull request