Impose
Impose 是一个简单的 Swift 依赖注入库
要求
- Swift 5.0 或更高版本(或当使用 Swift Package Manager 时为 5.3)
- iOS 9.3 或更高版本(或当使用 Swift Package Manager 时为 10)
- macOS 10.10 或更高版本
- tvOS 10 或更高版本
- WatchOS 4 或更高版本
安装
CocoaPods
Impose 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中
pod 'Impose', '~> 3.4.0'
从 XCode 开始使用 Swift 包管理器
- 使用 XCode 菜单中的 文件 > Swift 包 > 添加包依赖 并将其添加
- 将 https://github.com/hainayanda/Impose.git 作为 Swift 包 URL 添加
- 在 版本 中设置规则,选择 直到下一个主要版本 选项,并将其版本设置为 3.4.0
- 点击下一步并等待
从 Package.swift 使用 Swift 包管理器
在 Package.swift 中将其添加为目标依赖项
dependencies: [
.package(url: "https://github.com/hainayanda/Impose.git", .upToNextMajor(from: "3.4.0"))
]
在您的目标中将其用作 Impose
.target(
name: "MyModule",
dependencies: ["Impose"]
)
作者
Nayanda Haberty, [email protected]
许可协议
Impose 遵守 MIT 许可协议。有关更多信息,请参阅 LICENSE 文件。
基本用法
Impose的使用非常简单直观,您只需要提供一些依赖项提供商
Injector.shared.addSingleton(for: Dependency.self, SomeDependency())
然后在您的某些类中使用属性包装器或使用全局函数来使用它
class InjectedByPropertyWrapper {
@Injected var dependency: Dependency
...
...
}
class InjectedByInit {
var dependency: Dependency
init(dependency: Dependency = inject(Dependency.self)) {
self.dependency = dependency
}
}
提供者是autoClosure类型的,所以您可以这样做
Injector.shared.addSingleton(for: Dependency.self) {
let dependency: SomeDependency = .init()
dependency.doSomeSetup()
return dependency
}
提供者将自动只从调用闭包创建一个实例,并重复使用该实例,因此闭包只调用一次。如果想要提供者对每个注入调用闭包,可以使用addTransient
方法
Injector.shared.addTransient(for: Dependency.self, SomeDependency())
别忘了,如果提供者尚未注册,它将抛出一个不可捕获的错误。如果想要手动捕获错误,只需使用tryInject
代替
class InjectedByInit {
var dependency: Dependency
init(dependency: Dependency? = nil) {
do {
self.dependency = dependency ?? try tryInject(for: Dependency.self)
} catch {
self.dependency = DefaultDependency()
}
}
}
安全注入
有时您不希望您的应用程序因依赖项注入失败而抛出错误。在这些情况下,只需使用@SafelyInjected
属性或injectIfProvided
函数。如果注入失败,它会返回nil
class InjectedByPropertyWrapper {
@SafelyInjected var dependency: Dependency?
...
...
}
class InjectedByInit {
var dependency: Dependency
init(dependency: Dependency? = injectIfProvided(for: Dependency.self)) {
self.dependency = dependency
}
}
总是可以给闭包调用,如果注入失败
class InjectedByInit {
var dependency: Dependency
init(dependency: Dependency? = inject(Dependency.self, ifFailUse: SomeDependency())) {
self.dependency = dependency
}
}
单例提供者
最简单的注入提供者是单例提供者。该提供者只创建一个实例,存储它并重用该实例,因此闭包只调用一次。实例将不会释放,直到注入器释放。它对于共享实例依赖项非常有用
Injector.shared.addSingleton(for: Dependency.self, SomeDependency())
瞬态依赖提供者
与单例(Singleton)不同,瞬态依赖不会存储依赖,每次需要时都会重新创建依赖。不过闭包将被强存储。这对于存储无数据的服务很有用。
Injector.shared.addTransient(for: Dependency.self, SomeDependency())
弱依赖提供者
此提供者是单例和瞬态提供者的组合。它将在返回之前将实例存储在弱变量中。一旦存储的实例变为nil,它将为下一次注入重新创建一个新的实例。不过闭包将强存储。这对于那些希望在不再使用时释放的依赖项很有用。
Injector.shared.addWeakSingleton(for: Dependency.self, SomeDependency())
环境
使用Environment对象,您可以定义特定对象的可特定环境,该环境将伴随该对象并成为依赖项的主要来源。
Environment.forObject(myObject)
.inject(for: Dependency.self, SomeDependency())
.inject(for: AnotherDependency.self, SomeOtherDependency())
在上面的代码中,myObject
的Injected
属性包装器将使用环境作为依赖项的主要来源。如果依赖项未由环境提供,它将搜索Injector.shared
。
您可以将一个对象的依赖项提供者从另一个对象的Environment转移到另一个,以便它使用相似的环境。
Environment.fromObject(myObject, for: someObject)
.inject(for: SomeOtherDependency.self, SomeDependency())
在上面的代码中,someObject将有一个新的环境,其中包含myObject的所有依赖项提供者,还包含稍后添加的一个。如果手动分配,它还将填充来自myObject Injected属性包装器的依赖项。
class MyObject {
@Injected manual: ManualDependency
init() {
// this dependency will be transfered to another Environment created from this object
manualDependency = MyManualDependency()
}
}
循环依赖性
Injected
和SafelyInjected
属性包装器会懒加载依赖,因此即使你有循环依赖它也能工作。但是如果你使用inject函数而不是init进行解析,它将立即解析依赖,这将引发堆栈溢出错误。尽管循环依赖不推荐使用,但使用属性包装器进行注入以避免这个问题会更好。
单一提供者的多种类型
如果你需要,可以为单个提供者注册多个类型
Injector.shared.addSingleton(for: [Dependency.self, OtherDependency.self], SomeDependency())
或者为短暂需要注册
Injector.shared.addTransient(for: [Dependency.self, OtherDependency.self], SomeDependency())
甚至为环境
Environment.forObject(myObject).inject(for: [Dependency.self, OtherDependency.self], SomeDependency())
多重注入器
你可以为同一种类型的依赖有多个Injector
来提供不同依赖
Injector.shared.addTransient(for: Dependency.self, Primary())
let secondaryInjector = Injector()
secondaryInjector.addTransient(for: Dependency.self, Secondary())
要使用其他注入器,只需切换即可
Injector.switchInjector(to: secondaryInjector)
要切换回原来的,就像调用一个空方法一样简单
Injector.switchToDefaultInjector()
模块提供者
如果你有一个模块化项目,并希望每个模块都能自己手动注入一切。你可以使用ModuleProvider
协议,并在主模块中将其用作提供者
// this is in MyModule
class MyModuleInjector: ModuleProvider {
func provide(for injector: Injector) {
injector.addSingleton(for: Dependency.self, SomeDependency())
}
}
比如在你的AppDelegate
import Impose
import MyModule
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
provideDependencies()
// do something
return true
}
func provideDependencies() {
Injector.shared.provide(using: MyModuleInjector())
}
}
它将使用给定的 Injector
调用 provide(using:)
。您可以添加任意数量的 ModuleProvider
,但如果模块提供了同一类型解析器的相同依赖项,它将使用新的一个覆盖先前的。
AutoInjectMock
如果您要进行单元测试并需要模拟一些依赖项,您可以在测试准备中跳过注入,并在您的模拟对象上实现 AutoInjectMock
如下所示
class MyServiceMock: MyServiceProtocol, AutoInjectMock {
static var registeredTypes: [Any.Type] = [MyServiceProtocol.self]
..
..
}
然后在您的单元测试中,您可以使用它如下所示
serviceMock = MyServiceMock().injected()
// or
serviceMock = MyServiceMock().injected(using: customInjector)
别忘了,您应该使用类实例来使它生效,因为如果您使用的是结构体,由于其性质,注入的实例将不同。
Contribute
你知道怎么做,只需克隆并提交拉取请求