InjectGrail
该项目完全功能正常,但需要在几个方面给予很多关注
- 文档,
- 示例,
- 测试,
- 其他与过程相关的材料,
- 对生成的代码中的注释和其他可读性改进,
- Sourcery 模板的可读性改进,
- 基本框架信息。为什么,灵感等...
如果你愿意帮忙,我们非常欢迎!我们欢迎 PR。
TL;DR
这是
class MessagesViewController: UIViewController {
private let networkProvider: NetworkProvider
private let authProvider: AuthProvider
private let localStorage: LocalStorage
private let viewModel: MessagesViewModel
init(networkProvider: NetworkProvider, authProvider: AuthProvider, localStorage: LocalStorage, ...) {
self.networkProvider = networkProvider
self.authProvider = authProvider
self.localStorage = localStorage
self.viewModel = MessagesViewModel(networkProvider: networkProvider, authProvider: authProvider, localStorage: localStorage, ...)
}
}
// ------------------------------------------------------------------
class MessagesViewModel {
let networkProvider: NetworkProvider
let authProvider: AuthProvider
let localStorage: LocalStorage
init(networkProvider: NetworkProvider, authProvider: AuthProvider, localStorage: LocalStorage, ...) {
self.networkProvider = networkProvider
self.authProvider = authProvider
self.localStorage = localStorage
self.authProvider.checkifLoggedIn()
}
}
变成这样了
protocol MessagesViewControllerInjector: Injector {
}
class MessagesViewController: UIViewController, Injectable, InjectsMessagesViewModelInjector {
let injector: MessagesViewControllerInjectorImpl
init(injector: MessagesViewControllerInjectorImpl) {
self.injector = injector
self.viewModel = MessagesViewModel(inject())
}
}
// ------------------------------------------------------------------
protocol MessagesViewModelInjector: Injector {
var networkProvider: NetworkProvider {get}
var authProvider: AuthProvider {get}
var localStorage: LocalStorage {get}
}
class MessagesViewModel: Injectable {
let injector: MessagesViewModelInjectorImpl
init(injector: MessagesViewModelInjectorImpl) {
self.injector = injector
self.authProvider.checkifLoggedIn()
}
}
- 对于你声明的每个类,只包含它需要的依赖项。不是它的子类。
- 你不会得到一个大袋子,里面有所有你要携带到你的项目中所有类的依赖项。
- 依赖项将通过层次结构自动推送,而不接触父类定义,
- 每个类的
init
中只包含它真正需要或其子类(包裹在一个简单的结构体中)的依赖项, - 你的类仍然可以手动构建,
inject
函数接受当前类中未找到但由子类需要的依赖项作为参数,- 没有一行魔法。你可以通过 Cmd+Click 来查看确切的定义。只使用 DI 协议,结构体和扩展来实现。
- 所有内容的命令补全。
术语摘要
Injector
- 类的依赖项规范Injectable
- 需要其依赖项进行注入的类(通过init
中的Injector
进行注入)InjectsXXX
- 需要实现希望注入XXX
注入器的父类。RootInjector
- 实现此协议的类或结构体会自动能够注入所有Injectors
。这是注入树的顶端。必须正好有一个类实现此协议。
要求
安装
InjectGrail 通过 CocoaPods 提供。要安装它,只需将以下行添加到 Podfile 中
pod 'InjectGrail'
用法
-
import InjectGrail
-
对于每个需要被标记为
Injectable
(而不是直接传递参数到init
)的类,创建一个将指定它们的协议,让它遵守Injector
协议。例如,假设我们有一个
MessagesViewModel
,我们希望它是可注入的。class MessagesViewModel { let networkManager: NetworkManager init(networkManager: NetworkManager) { self.networkManager = networkManager } }
我们需要创建
MessagesViewModelInjector
- 名称不重要。按照惯例,我们使用<InjectableClassName>Injector
并让它遵守Injector
protocol MessagesViewModelInjector: Injector { var networkManager: NetworkManager {get} }
-
添加新的构建脚本(在编译之前)
"$PODS_ROOT/InjectGrail/Scripts/inject.sh"
-
添加一个实现
RootInjector
的类或结构体。这将是你最高级别的注入器,能够注入其他所有Injectables
。也可以手动创建注入器。struct RootInjectorImpl: RootInjector { let networkManager: NetworkManager let messagesRepository: MessagesRepository let authenticationManager: AuthenticationManager }
-
编译。注入脚本将在项目目录中生成文件
/Generated/Inject.generated.swift
。将其添加到项目中。 -
对于每个需要成为
Injectable
的类,让它实现Injectable
并通过创建字段injector
和init(injector:...)
来满足协议要求。 实际可以使用的数据结构是由注入框架根据您的Injector
定义创建的。例如,针对我们的MessagesViewModel
,我们创建了协议MessagesViewModelInjector
,注入框架创建了结构体MessagesViewModelInjectorImpl
(添加了Impl
)的实现。我们应该使用它。class MessagesViewModel: Injectable { let injector: MessagesViewModelInjectorImpl init(injector: MessagesViewModelInjectorImpl) { self.injector = injector } }
所有来自
MessagesViewModelInjector
的属性都可以通过由InjectGrail
自动创建的扩展直接在MessagesViewModel
中使用。因此在这种情况下,我们可以直接使用networkManager
。class MessagesViewModel: Injectable { let injector: MessagesViewModelInjectorImpl init(injector: MessagesViewModelInjectorImpl) { self.injector = injector } func doSomeAction() { self.networkManager.callBackend() } }
-
对于每个
Injector
,InjectGrail
还创建一个协议Injects<InjectorName>
,在我们的例子中就是InjectsMessagesViewModelInjector
。自身是Injectable
且希望能够注入到其他Injectables
的类可以通过采用该协议来创建一个辅助函数inject(...)
,这个函数不进行注入。InjectGrail
会自动解析当前类的Injector
和目标Injector
之间的依赖关系,并为inject
函数添加所有尚未找到的参数。采用Injects<InjectorName>
也会将目标的所有依赖项添加到当前的注入器Impl
中。如果我们需要从
MessagesViewModel
创建MessageRowViewModel
,我们需要创建MessageRowViewModelInjector
并像这样让MessageRowViewModel
实现Injectable
protocol MessageRowViewModelInjector: Injector { var messagesRepository: MessagesRepository {get} var messageIndex: Int {get} } class MessageRowViewModel: Injectable { let injector: MessageRowViewModelInjectorImpl init(injector: MessageRowViewModelInjectorImpl) { self.injector = injector } }
在运行注入脚本后,我们可以使
MessagesViewModel
实现InjectsMessageRowViewModelInjector
,并在脚本下一次运行后,MessagesViewModelInjectorImpl
会自动获取额外的属性messagesRepository
—— 因为它是由RootInjector
提供的,而MessagesViewModel
将扩展func inject(messageIndex: Int) -> MessageRowViewModelInjector
函数,它可以用于创建MessageRowViewModel
,如下所示class MessagesViewModel: Injectable { let injector: MessagesViewModelInjectorImpl init(injector: MessagesViewModelInjectorImpl) { self.injector = injector } func createRowViewModel() { let rowViewModel = MessageRowViewModel(inject(messageIndex: 0)) } }
Int
和String
在注入过程中从不被解析。即使注入的类在其Injector
中也有它。解析也可以通过在Injector
中的字段添加 Sourcery 注解来手动禁用。protocol MessageRowViewModelInjector: Injector { var messagesRepository: MessagesRepository {get} // sourcery: forceManual var authenticationManager: AuthenticationManager {get} var messageIndex: Int {get} }
在上面的例子中,
authenticationManager
将始终来自注入类inject
函数的参数。
解析逻辑
当针对父注入器解析依赖关系时,InjectGrail
通过类型定义进行搜索。如果有多个同类型的属性,则它还会通过名称进行匹配。如上所述,Int
和 String
从不被解析。
作者
Łukasz Kwoska,[email protected]
许可协议
InjectGrail 适用于 MIT 许可证。有关更多信息,请参阅 LICENSE 文件。
致谢
- 本项目无法成立离不开 Sourcery。它是幕后主要组件。
- Annotation Inject - 感谢展示给我如何轻松地从 pod 使用 Sourcery。