CocoaPods CocoaPods Install License

EasyInject

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 ManagerCarthageCocoaPods

Swift Package Manager

import PackageDescription

let package = Package(
    name: "YourPackage",
    dependencies: [
        .Package(url: "https://github.com/vknabel/EasyInject.git", majorVersion: 1)
    ]
)

Carthage

github "vknabel/EasyInject" ~> 1.2

CocoaPods

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")
    }
}

LazyInjector

有几种 Injector 可供选择,例如 StrictInjectorLazyInjector。让我们先选择惰性的那个,并为我们的键提供一些值。

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

当使用 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)")
}

GlobalInjector

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

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 许可证下可用。