EasyInject is designed to be an easy to use, lightweight composition and dependency injection library.
Instead of injecting instances for specific types, you provide instances for keys, without losing any type information. This enables its Injectors to be used as a composable, dynamic and typesafe data structure. It may be comparable with a Dictionary that may contain several types, without losing type safety.
EasyInject supports Swift 3 and Swift 4 since version 1.2.0.
Values can only accessed by subscripts in Swift 4, if you are still using Swift 3, keep using Injector.resolving(for:).
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
pod 'EasyInject', '~> 1.2'
Introduction
In order to inject your dependencies, you first need to prepare your key by implementing Hashable.
import EasyInject
enum ServicesKeyType { } // will never be instantiated
typealias Services = GenericProvidableKey<Services>
Now we need to define our keys, by setting up Providers with Strings and our type hints.
extension Provider {
static var baseUrl: Provider<Services, String> {
return Provider<Services, String>(for: "baseUrl")
}
static var networkService: Provider<Services, NetworkService> {
// produces a key of `networkService(...) -> Network`.
return .derive()
}
static var dataManager: Provider<Services, DataManager> {
return .derive()
}
}
final class NetworkService {
let baseUrl: String
init<I: Injector where I.Key == Services>(injector: inout I) throws {
print("Start: NetworkService")
baseUrl = try injector[.baseUrl] // or resolving(from: .baseUrl) in Swift 3.x
print("Finish: NetworkService")
}
}
final class DataManager {
let networkService: NetworkService
init<I: Injector where I.Key == Services>(injector: inout I) throws {
print("Start: DataManager")
networkService = try injector[.networkService]
print("Finish: DataManager")
}
}
LazyInjector
There are some Injectors to choose, like a StrictInjector or LazyInjector.
Let’s pick the lazy one first and provide some values for our keys.
var lazyInjector = LazyInjector<Services>() // Only Services keys will fit in here
lazyInjector.provide(for: .baseUrl, usingFactory: { _ in
print("Return: BasUrl")
return "https://my.base.url/"
})
lazyInjector.provide(for: .dataManager, usingFactory: DataManager.init)
lazyInjector.provide(for: .networkService, usingFactory: NetworkService.init)
Since we are using the LazyInjector, no closure we passed has been executed yet.
They will only be executed when they get resolved.
// this will execute all factories we passed for our providers
do {
try lazyInjector.resolve(from: .dataManager)
} catch {
print("Error: \(error)")
}
Because we picked LazyInjector, all dependencies will be resolved automatically, when needed. Therefore the produced output would be:
So because of the laziness of out LazyInjector, all dependencies will be resolved automatically.
Cyclic dependencies throw an error on being resolved to prevent endless recursions.
StrictInjector
The previous example would fail when using StrictInjector, because we provided .dataManager before providing .networkService, but DataManager requires a .networkService.
A GobalInjector wraps another Injector in order to make it act like a class.
let globalInjector = GlobalInjector(injector: strictInjector)
let second = globalInjector
// `globalInjector` may be mutated as it is a class.
second.provide("https://vknabel.github.io/EasyInject", for: .baseUrl)
if let left = try? globalInjector.resolve(from: .baseUrl),
let right = try? globalInjector.resolve(from: .baseUrl),
left == right {
// both `right` and `left` contain `"https://vknabel.github.io/EasyInject"` for `.baseUrl` due to reference semantics
}
ComposedInjector
A ComposedInjector consists of two other Injectors.
The call .resolve(from:) will target the .leftInjector and on failure, the .right one.
.provide(for:,usingFactory:) defaults to .provideLeft(for:,usingFactory:) which will provide the factory only to the .left one.
Usually the left Injector will be the local one, whereas the right one is a global one. This makes it possible to cascade ComposedInjectors from your root controller down to your leaf controllers.
EasyInject
EasyInject is designed to be an easy to use, lightweight composition and dependency injection library. Instead of injecting instances for specific types, you provide instances for keys, without losing any type information. This enables its
Injectors to be used as a composable, dynamic and typesafe data structure. It may be comparable with a Dictionary that may contain several types, without losing type safety.Check out the generated docs at vknabel.github.io/EasyInject.
EasyInject supports Swift 3 and Swift 4 since version 1.2.0. Values can only accessed by subscripts in Swift 4, if you are still using Swift 3, keep using
Injector.resolving(for:).Installation
EasyInject is a Swift only project and supports Swift Package Manager, Carthage and CocoaPods.
Swift Package Manager
Carthage
CocoaPods
Introduction
In order to inject your dependencies, you first need to prepare your key by implementing
Hashable.Now we need to define our keys, by setting up
Providers withStrings and our type hints.LazyInjector
There are some
Injectors to choose, like aStrictInjectororLazyInjector. Let’s pick the lazy one first and provide some values for our keys.Since we are using the
LazyInjector, no closure we passed has been executed yet. They will only be executed when they get resolved.Because we picked
LazyInjector, all dependencies will be resolved automatically, when needed. Therefore the produced output would be:So because of the laziness of out
LazyInjector, all dependencies will be resolved automatically. Cyclic dependencies throw an error on being resolved to prevent endless recursions.StrictInjector
The previous example would fail when using
StrictInjector, because we provided.dataManagerbefore providing.networkService, butDataManagerrequires a.networkService.The output would be:
This behavior may be helpful when debugging your
LazyInjectorin order to detect dependency cycles.You may fix this error, just by flipping the lines with
.networkServiceand.dataManager, and that would lead to the following output:GlobalInjector
A
GobalInjectorwraps anotherInjectorin order to make it act like a class.ComposedInjector
A
ComposedInjectorconsists of two otherInjectors. The call.resolve(from:)will target the.leftInjectorand on failure, the.rightone..provide(for:,usingFactory:)defaults to.provideLeft(for:,usingFactory:)which will provide the factory only to the.leftone.Usually the left
Injectorwill be the local one, whereas the right one is a global one. This makes it possible to cascadeComposedInjectors from your root controller down to your leaf controllers.Author
Valentin Knabel, dev@vknabel.com
License
EasyInject is available under the MIT license.