一个基于服务定位器模式并利用 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))
通过这样做,在生产代码中所做的注册可以被测试中的模拟对象覆盖,而这些模拟对象不会在对象之间共享。
实例也可以使用多个别名协议进行注册,每个协议仅公开其功能的某些部分
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)
}
}
可以通过使 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
方法。
注册和解析依赖项都在专用的同步和可重入队列上处理。