EasyInject 旨在成为一个易于使用、轻量级的组合和依赖注入库。它不为特定类型注入实例,而是为键提供实例,同时不丢失任何类型信息。这使得它的 Injector
可以用作可组合、动态且类型安全的数据结构。它可以与一个可能包含多种类型的字典相媲美,而不会损失类型安全性。
请查看生成的文档:vknabel.github.io/EasyInject。
EasyInject 从 1.2.0 版本开始支持 Swift 3 和 Swift 4。在 Swift 4 中,值只能通过下标访问;如果您仍在使用 Swift 3,请继续使用 Injector.resolving(for:)
。
EasyInject 是一个纯 Swift 项目,支持 Swift Package Manager、Carthage 和 CocoaPods。
import PackageDescription
let package = Package(
name: "YourPackage",
dependencies: [
.Package(url: "https://github.com/vknabel/EasyInject.git", majorVersion: 1)
]
)
github "vknabel/EasyInject" ~> 1.2
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
pod 'EasyInject', '~> 1.2'
为了注入您的依赖项,您首先需要通过实现 Hashable
来准备您的键。
import EasyInject
enum ServicesKeyType { } // will never be instantiated
typealias Services = GenericProvidableKey<Services>
现在我们需要通过使用 String
和类型提示设置 Provider
来定义我们的键。
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")
}
}
有几种 Injector
可供选择,例如 StrictInjector
或 LazyInjector
。让我们先选择惰性的那个,并为我们的键提供一些值。
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)
由于我们使用的是 LazyInjector
,因此我们传递的任何闭包都尚未执行。它们只会在被解析时执行。
// this will execute all factories we passed for our providers
do {
try lazyInjector.resolve(from: .dataManager)
} catch {
print("Error: \(error)")
}
因为我们选择了 LazyInjector
,所以所有依赖项都将在需要时自动解析。因此,产生的输出将是
Start: DataManager
Start: NetworkService
Return: BasUrl
Finish: NetworkService
Finish: DataManager
因此,由于 LazyInjector
的惰性,所有依赖项都将自动解析。循环依赖会在解析时抛出错误,以防止无限递归。
当使用 StrictInjector
时,前面的示例将失败,因为我们在提供 .networkService
之前提供了 .dataManager
,但 DataManager
需要 .networkService
。
var strictInjector = StrictInjector<Services>()
strictInjector.provide(for: .baseUrl, usingFactory: { _ in
print("Return: BaseUrl")
return "https://my.base.url/"
})
strictInjector.provide(for: .dataManager, usingFactory: DataManager.init) // <-- missing .networkService
strictInjector.provide(for: .networkService, usingFactory: NetworkService.init)
do {
try strictInjector.resolve(from: .dataManager)
} catch {
print("Error: \(error)")
}
输出将是
Return: BaseUrl
Start: DataManager
Start: NetworkService
Finish: NetworkService
Error: keyNotProvided("networkService(...) -> NetworkService")
当调试您的 LazyInjector
以检测依赖循环时,此行为可能很有帮助。
您可以通过交换 .networkService
和 .dataManager
的行来修复此错误,这将导致以下输出
Return: BaseUrl
Start: NetworkService
Finish: NetworkService
Start: DataManager
Finish: DataManager
strictInjector = StrictInjector<Services>()
strictInjector.provide(for: .baseUrl, usingFactory: { _ in
print("Return: BaseUrl")
return "https://my.base.url/"
})
strictInjector.provide(for: .networkService, usingFactory: NetworkService.init)
strictInjector.provide(for: .dataManager, usingFactory: DataManager.init)
do {
try strictInjector.resolve(from: .dataManager)
} catch {
print("Error: \(error)")
}
GobalInjector
包装另一个 Injector
,使其表现得像一个类。
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
由两个其他的 Injector
组成。调用 .resolve(from:)
将首先指向 .left
Injector
,如果失败,则指向 .right
Injector
。 .provide(for:,usingFactory:)
默认使用 .provideLeft(for:,usingFactory:)
,这将仅向 .left
Injector
提供工厂。
通常,左侧的 Injector
将是本地的,而右侧的 Injector
是全局的。这使得从您的根控制器到叶控制器级联 ComposedInjector
成为可能。
var composedInjector = ComposedInjector(left: StrictInjector(), right: globalInjector)
composedInjector.provideLeft("https://vknabel.github.io/EasyInject/Structs/ComposedInjector.html", for: .baseUrl)
do {
try composedInjector.resolveBoth(from: .baseUrl)
// returns `("https://vknabel.github.io/EasyInject/Structs/ComposedInjector.html", "https://vknabel.github.io/EasyInject")`
} catch {
print("Error: \(error)")
}
Valentin Knabel, dev@vknabel.com
EasyInject 在 MIT 许可证下可用。