DependencyInjection
基于服务定位模式的依赖注入微框架,利用 Swift 的属性包装器。
使用方法
在 App 启动时,通过覆盖 AppDelegate 的初始化器来注册依赖项
import DependencyInjection
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
@LazyInject private var config: AppConfiguration
@LazyInject private var router: Router
override init() {
super.init()
DIContainer.register {
Shared(AppConfigurationImpl() as AppConfiguration)
Shared(RouterImpl() as Router)
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// use `config` and `router`
return true
}
}
懒加载与急加载评估
如上所述,由于注入属性的初始化器会先于 AppDelegate 的初始化器调用,因此有必要将注入属性的使用和初始化分开。`@LazyInject` 仅在属性首次访问时解析。要解析属性,请使用 `@Inject`。
可变与不可变注入属性
通过 `@Inject
` 和 `@LazyInject
` 注入的属性是不可变的,这由编译器强制执行。要用 `@MutableInject` 和 `@MutableLazyInject
` 解析可变属性。
可选依赖
在特定情况下无法解析的依赖项可以简单地标记为Optional
。
@Inject var player: MediaPlayer?
如果没有注册MediaPlayer
实例,则player
解析为nil
。注意:如果将Optional实例注册,则player
也解析为nil
,因为注册的类型不是预期的MediaPlayer
,而是此处的Optional<MediaPlayer>
。
共享与新实例
将依赖项注册为Shared
将始终解析为相同的(相同的)实例。要获取每个属性的新实例,请使用New
。
DIContainer.register(New(MockRouter() as Router))
这样做可以在生产代码中覆盖注册的依赖项,例如,在不共享对象的情况下在测试中使用mock对象。
别名
还支持使用多个别名协议注册实例,每个协议仅公开其功能的一部分
DIContainer.register(Shared(RouterImpl.init, as: Router.self, DeeplinkHandler.self))
注册具有自身依赖的依赖项
如果注册的依赖项依赖于其他依赖项,应该通过初始化器注入传递,则存在用于注册Shared
和New
实例的重载,它们在闭包中传递一个Resolver
对象
DIContainer.register {
Shared { resolve in RouterImpl(config: resolve()) }
}
模块
为了对依赖进行分组或避免在Swift模块外暴露具体类型,可以使用依赖注入(DI)模块。这些模块是注册的便捷包装器,可以定义在代码库的不同部分,然后自行注册,例如在AppDelegate中。
// Feature 1
struct FeatureOneDependencyInjection {
static let module = Module {
Shared(FeatureOneImplementation() as FeatureOne)
New(FeatureOneViewModelImplementation() as FeatureOneViewModel)
}
}
// Feature 2
struct FeatureTwoDependencyInjection {
static let module = Module(Shared(FeatureTwoImplementation() as FeatureTwo))
}
// AppDelegate
override init() {
super.init()
DIContainer.register {
FeatureOneDependencyInjection.module
FeatureTwoDependencyInjection.module
}
}
或者可以在中心位置使用模块,直接进行内联注册。
DIContainer.register {
Module {
Shared(FeatureOneImplementation() as FeatureOne)
New(FeatureOneViewModelImplementation() as FeatureOneViewModel)
}
Module {
Shared(FeatureTwoImplementation() as FeatureTwo)
}
}
在AppDelegate之外进行替代注册
可以通过实现依赖于DIContainer
和DependencyRegistering
协议以及实现registerDependencies
方法来注册依赖。一旦解决第一个依赖项,则注册依赖项。
extension DIContainer: DependencyRegistering {
public static func registerDependencies() {
register(Shared(RouterImpl() as Router))
}
}
没有使用属性包装器的使用方法
由于当前无法在函数体中使用属性包装器,因此依赖项可以“手动”解决。
func foo() {
DIContainer.resolve(Router.self)
}
或者如果编译器可以推断要解决的类型
func foo() {
bar(router: DIContainer.resolve())
}
func bar(router: Router) {
// …
}
参数化解析
如果需要在稍后提供某些或所有参数的类型上也应用控制反转,则可以注册一个接收参数并返回所需对象的闭包。在这种情况下,注册一个New
实例最有意义,这意味着每次解决依赖项时都会创建一个新对象。如果注册一个Shared
实例,则解决的实例将始终是针对相应类型首先解决的实例,而参数将被忽略。
func register() {
DIContainer.register {
New({ resolver, id in ConcreteViewModel(id: id) }, as: ViewModelProtocol.self)
// alternatively:
New { _, id in ConcreteViewModel(id: id) as ViewModelProtocol }
}
}
必须提供id
参数才能使用实现ViewModelProtocol
的实例。第一个参数resolver
可以用来或忽略以通过依赖注入解决其他参数。提供参数是在闭包中完成的。
func resolve() -> ViewModelProtocol {
DIContainer.resolve(ViewModelProtocol.self, arguments: { "id_goes_here" })
}
func resolve() -> PresenterProtocol {
// multiple arguments are provided as tuple:
DIContainer.resolve(PresenterProtocol.self) { ("argument 1", 23, "argument 3") }
}
由于Swift中属性(以及相应的属性包装器)的初始化器在self可用之前被调用,因此可以在@Inject
和@LazyInject
的初始化器中提供硬编码的参数。因此,参数化解析当前仅限于如上所示的DIContainer
的resolve
方法。
线程安全性
注册和解析依赖都在一个专门的同步和可重入队列中处理。