Zerk
Zerk 是一个针对 Swift 编写的框架,它允许您按照 依赖注入模式 和 Clean Coding 原则 协议轻松地存储和恢复依赖项。它消除了静态方法和单例的需要,从而有助于创建更易于管理和测试的全局或局部依赖项。
功能
-
Zerk 提供了一种存储依赖项初始化逻辑的方法。这些依赖项可以是短、中或长期存在的对象,并且在存储时不进行初始化。当依赖项被调用时,如果之前未进行初始化,则将对其进行初始化,并且只有在需要通过后续调用来使用实例时,才会将实例缓存。
-
存储的依赖项可以轻松且无任何麻烦地恢复,因此可以通过初始化器、方法或属性进行注入。
-
依赖项可以依赖于其他存储的依赖项。它们甚至可以在初始化时提供参数。
-
有了新的
@Injected
关键字,注入的依赖项将自动恢复,从而使代码更加易于阅读。 -
还有其他关键字,用于仅注入您依赖项的属性或方法,使用了 Swift 的尖端的键路径功能。
需求
- iOS 9.0+
- Xcode 13+
- Swift 5.0+
- CocoaPods 1.1.1+(如果使用的话)
安装
Zerk 通过 CocoaPods 和 Swift 包管理器 提供。
CocoaPods
要使用 CocoaPods 安装 Zerk,请将以下行添加到您的 Podfile
。
source 'https://cdn.cocoapods.org/'
platform :ios, '9.0'
use_frameworks!
pod 'Zerk'
然后运行 pod install
命令。有关 CocoaPods 的安装和使用详细信息的详细信息,请访问其官方网站。
Swift 包管理器
在 Package.swift
中添加以下内容
dependencies: [
...
.package(url: "https://github.com/ofgokce/Zerk.git", from: "1.0.0")
],
targets: [
.target(
name: "MyProject",
dependencies: [..., "Zerk"]
)
...
]
基本用法
存储
首先,应在存储中存储依赖项的初始化逻辑。对于大多数情况,可以使用 Zerk.store
存储所有依赖项。
存储的依赖项只能由存储它们的类型进行恢复。依赖项可以作为它们所符合的多个类型存储,但对于任何给定的类型,应该只有一个依赖项。建议将它们存储为协议以提高可测试性。
依赖项可以使用三种生命周期选项进行存储
-
转瞬即逝:这些依赖项在每次被调用时都会初始化。
-
作用域:这些依赖项在每次被恢复功能恢复时都会初始化。此类型用于 Zerk 的属性包装器以确保实例的生命周期与其所依赖的对象的生命周期一样长。
-
单例:这些依赖项在第一次调用时只初始化一次,然后它们作为实例存储在存储中,并可以在全局范围内使用。
依赖将以包装类Dependency
的形式存储,该类负责识别、创建、类型转换和实例持有这些依赖。该类有一个getInstance(with:)
方法,它会在实例尚未创建时创建实例,或者在实例已被实例化时返回存储的单例实例。
- 存储不依赖其他依赖的依赖
Zerk.store
.transient(TransientDependencyClass() as TransientDependencyProtocol)
.scoped(ScopedDependencyClass() as ScopedDependencyProtocol)
.singleton(SingletonDependencyClass() as SingletonDependencyProtocol)
- 存储依赖其他依赖的依赖
Zerk.store
.transient { dependency in
DependentClassA(dependency: dependency) as DependentProtocolA
}
.scoped { dependency0, dependency1, dependency2, dependency3, dependency4 in
DependentClassB(dependency0: dependency0, dependency1: dependency1, dependency2: dependency2, dependency3: dependency3, dependency4: dependency4)
as DependentProtocolB
}
.singleton {
DependentClassC(dependency0: $0, dependency1: $1, dependency2: $2, dependency3: $3, dependency4: $4)
as DependentProtocolC
}
- 存储依赖其他依赖的依赖(通过存储恢复)
Zerk.store
.transient { storage in
DependentClassA(dependency: storage.restore())
as DependentProtocolA
}
.scoped { storage in
DependentClassB(dependency0: storage.restore(), dependency1: storage.restore())
as DependentProtocolB
}
- 存储具有参数化初始化的依赖
Zerk.store
.transient { storage, arguments in
ArgumentativeClassA(parameterName: arguments.parameterName)
as ArgumentativeProtocolA
}
.singleton { storage, arguments in
ArgumentativeClassB(dependency: storage.restore(), parameterName0: arguments.parameterName0, parameterName1: arguments.customName)
as ArgumentativeProtocolB
}
参数名称既不名称安全,也不类型安全。使用了Swift的dynamicCallable和dynamicMemberLookup注解来提供此功能。用于存储依赖的命名和类型应与用于恢复它们的命名和类型相匹配。否则将抛出致命错误。有关更多信息,请参阅文档。
- 对于具有多个类型(别名)的依赖
Zerk.store
.scoped({ _, _ in
MultitypeClass()
}, as: ProtocolA.self, ProtocolB.self, ProtocolC.self)
.singleton({ storage, arguments in
MultitypeClass(dependency: storage.restore(), parameterName0: arguments.parameterName0, parameterName1: arguments.customName)
}, as: ProtocolA.self, ProtocolB.self, ProtocolC.self)
依赖应该能够转换为这里给出的类型。否则将抛出致命错误。
就这样!
恢复
现在,同一存储的restore()
、restore(with:)
和restore(_:)
方法可以恢复存储的依赖。前两个方法将返回依赖本身的类型转换实例,而后者将返回类型为Dependency
的包装实例。
let instanceA: DependencyProtocolA = Zerk.standardStorage.restore() // Returns the typecasted dependency instance
let instanceB: DependencyProtocolB = Zerk.standardStorage.restore(with: .arguments(argument0: value0, argument1: value1) // Returns the typecasted dependency instance, instantiated with the given arguments
或者
let dependency = Zerk.standardStorage.restore(DependencyProtocol) // Returns the wrapper instance
基本注入
有几种方式可以将依赖注入到类型中。
- 初始化器注入
class SomeClass {
private let dependency: DependencyProtocol
init(dependency: DependencyProtocol) {
self.dependency = dependency
...
}
}
let someInstance = SomeClass(dependency: Zerk.standardStorage.restore())
- 方法注入
class SomeClass {
private var dependency: DependencyProtocol?
func set(dependency: DependencyProtocol) {
self.dependency = dependency
}
}
let someInstance = SomeClass()
someInstance.set(dependency: Zerk.standardStorage.restore())
- 属性注入
class SomeClass {
var dependency: DependencyProtocol?
}
let someInstance = SomeClass()
someInstance.dependency = Zerk.standardStorage.restore()
关键字注入
Zero提供了新的关键字,使注入更加简单、易于阅读。
- @Injected
注入整个依赖。
class SomeClass {
@Injected var dependency: DependencyProtocol
}
注入具有参数化的依赖
class SomeClass {
@Injected(with: .arguments(argument0: value0, argument1: value1)
var dependency: DependencyProtocol
}
- @InjectedProperty
注入依赖的只读属性。应使用键路径语法。
class SomeClass {
@InjectedProperty(\DependencyProtocol.someProperty) var someProperty: SomeType
}
- @InjectedMutableProperty
注入依赖的可变属性。
class SomeClass {
@InjectedMutableProperty(\DependencyProtocol.someProperty) var someProperty: SomeType
}
- @InjectedUnwrappedProperty & @InjectedUnwrappedMutableProperty
解包并注入依赖的可选属性。如果已给出默认值,注入的属性将通过默认值解包。否则,属性将被强行解包。
class SomeClass {
@InjectedUnwrappedProperty(\DependencyProtocol.someProperty, default: someValue) var someProperty: SomeType
}
- @InjectedMethod
注入依赖的方法。由于Swift目前不支持方法的关键路径,这个包装必须使用不同的方式工作。
class SomeClass {
@InjectedMethod(DependencyProtocol.someMethod) var someMethod: (ParameterTypes) -> (ReturnTypes)
}
或者
class SomeClass {
@InjectedMethod(DependencyProtocol.someMethod(_:someParameter:)) var someMethod: (ParameterTypes) -> (ReturnTypes)
}
在哪里存储依赖
依赖在使用之前必须存储。
典型的方法是在AppDelegate
中存储它们,最好在退出application:didFinishLaunchingWithOptions:
方法之前。
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
...
Zerk.store
.transient( ... )
.singleton { ... }
...
...
}
}
为了在没有使AppDelegate杂乱的情况下自动化存储过程,你可以在一个空文件中将Zerk
符合到AutoStoring
协议。此协议只有一个store()
函数,该函数将在第一次需要任何依赖恢复时被调用一次。
extension Zerk: AutoStoring {
func store() {
Zerk.store
.transient( ... )
.singleton { ... }
...
}
}
注意
我将其中的大部分作为我为一个项目编写的DI解决方案编写,该项目在AppStore上有数百万用户使用。这是该应用的核心。我只是想和世界分享它,所以我对其进行了修改,并使其成为了一个框架。由于没有之前的文档,所以文档可能有些糟糕,但是我会随着时间的推移将其完善。我还会检查并更新要求和兼容平台。
如果你有使它变得更好的想法,请随时联系我。这将受到赞赏。最重要的是,玩得开心!
致谢
存储方法灵感来自
多类型(别名)灵感来自
许可证
MIT许可证。请参阅LICENSE文件以获取详细信息。