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
Injector
s 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
Provider
s withString
s and our type hints.LazyInjector
There are some
Injector
s to choose, like aStrictInjector
orLazyInjector
. 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.dataManager
before providing.networkService
, butDataManager
requires a.networkService
.The output would be:
This behavior may be helpful when debugging your
LazyInjector
in order to detect dependency cycles.You may fix this error, just by flipping the lines with
.networkService
and.dataManager
, and that would lead to the following output:GlobalInjector
A
GobalInjector
wraps anotherInjector
in order to make it act like a class.ComposedInjector
A
ComposedInjector
consists of two otherInjector
s. The call.resolve(from:)
will target the.left
Injector
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 cascadeComposedInjector
s from your root controller down to your leaf controllers.Author
Valentin Knabel, dev@vknabel.com
License
EasyInject is available under the MIT license.