SwiftiePod 是一个轻量级且易于使用的 Swift 依赖注入 (DI) 库。它的设计宗旨是简单直接、高效,并且最重要的是安全!
与其他许多 DI 库不同,SwiftiePod 确保您永远不会因为在尝试解析依赖项之前忘记注册它而遇到运行时异常。
在您的 Package.swift 文件中添加 SwiftiePod 作为依赖项
dependencies: [
.package(url: "https://github.com/robert-northmind/SwiftiePod.git", from: "1.0.8")
]
将此添加到您的 Podfile
pod 'SwiftiePod'
在一些虚构的业务逻辑代码中,例如 DataService.swift
import SwiftiePod
// Define a `Provider` for the `DataService`.
// A `Provider` is a SwiftiePod thing.
// It's a thing which knows how to build your types.
let dataServiceProvider = Provider<DataService> { pod in
return RemoteDataService(
httpClient: pod.resolve(httpClientProvider)
)
}
protocol DataService {
func fetchData() -> [String]
}
class RemoteDataService: DataService {
private let httpClient: HttpClient
init(httpClient: HttpClient) {
self.httpClient = httpClient
}
func fetchData() async throws -> [String] {
return await httpClient.get("/some/endpoint")
}
}
在您应用程序的启动代码中,设置您的 pod
import SwiftiePod
let pod = SwiftiePod()
最后,当您需要 DataService
的实例时,从 pod 中解析它
import SwiftiePod
let dataService = pod.resolve(dataServiceProvider)
// Now you can use the dataService...
有关更深入的示例,请参阅 ExampleApp。
大多数依赖注入库都包含一些容器,您可以在其中为给定类型注册一些构建器。 类似这样
// Setup and register stuff
let container = DIContainer()
container.register(DataService.self) { _ in
return RemoteDataService()
}
// Get your instance out of the container by passing the type
let dataService = container.resolve(DataService.self)!
这种方法有一些缺点
SwiftiePod 采用了不同的方法。 您无需在容器中注册构建器,而是为构建器定义一个变量,称为 Provider
,然后使用此 Provider
从容器中解析实例。 没有注册部分!
这样,容器始终知道如何构建您的实例。 永远不会因为未注册的类型而导致应用程序崩溃。
SwiftiePod 中的两个核心组件是 SwiftiePod
和 Providers
。
SwiftiePod
是您的容器。 您使用它来解析您的类型。
Providers
是您的构建器。 您将这些传递给 pod
以获取实例。 对于您想要拥有实例的任何事物,您都需要定义一个 Provider
。 它看起来像这样
let myCoolServiceProvider = Provider { _ in
return MyCoolService()
}
就是这样! 🥳
如上所述,Providers
是您的构建器,也是您用来解析类型的事物。 它们基本上是帮助您完成依赖注入的构建块。
对于您需要从中获取实例的每种类型,您都需要定义一个 Provider
。 然后,当您需要该类型的实例时,您可以通过将该 Provider
传递给 pod
来获取它。
let myCoolService = pod.resolve(myCoolServiceProvider)
如果您尝试构建的类型需要其他类型作为输入(依赖注入),那么您可以简单地使用提供的 pod
参数获取这些类型,该参数会传递到您的 Provider
的构建器方法中。
let someServiceProvider = Provider { pod in // <-- Here you get a reference to your pod
return SomeService(
// Use that pod here to get any needed dependencies
aDependency: pod.resolve(aDependencyProvider),
anotherDependency: pod.resolve(anotherDependencyProvider)
)
}
创建 Provider
的典型流程通常是在定义类型的文件的顶部
let myCoolClassProvider = Provider { _ in
return MyCoolClass()
}
class MyCoolClass {
// Some interesting business logic
}
如果您未指定 Provider
具有的类型,则它将隐式地获取与其返回值相同的类型。 您也可以显式指定类型。 例如,如果您有一个协议用作抽象层。
// Will have this type `Provider<Int>`
let aNumberProvider = Provider { _ in
return 123
}
// Will have this type `Provider<any CurrentUserServiceProtocol>`
let currentUserServiceProvider = Provider<CurrentUserServiceProtocol> { _ in
return CurrentUserService()
}
protocol CurrentUserServiceProtocol {
func username() -> String
}
class CurrentUserService: CurrentUserServiceProtocol {
func username() -> String {
return "Jane Doe"
}
}
当您创建 Provider
时,您还可以指定应如何缓存其实例。 您可以使用 Scope
参数来控制这一点。
开箱即用,SwiftiePod
为您提供了 2 个预定义的 scope:AlwaysCreateNewScope
和 SingletonScope
。
顾名思义,AlwaysCreateNewScope
永远不会缓存实例。 每次您使用此 scope 解析 provider 时,它都会运行构建器并创建一个新实例。
而 SingletonScope
将确保在您的 pod
的整个生命周期内缓存实例。
如果您没有将 scope
参数传递给您的 Provider
,那么它将默认使用 SingletonScope
。
您还可以通过实现 ProviderScope
协议来创建自己的自定义 scope。 这样,您可以为应用程序的给定流程定义一个 scope。 例如,您可以为应用程序中的注册流程设置一个特定的 scope。
final class SignUpScope: ProviderScope {
let children: [any ProviderScope] = []
}
let someSignUpProvider = Provider(scope: SignUpScope()) { _ in
return SomeSignUp()
}
当用户完成应用程序中的注册流程时,您可以轻松清除具有该 scope 的所有缓存实例。 这样,下次用户进入注册流程时,他将获得新的“干净”实例。
您甚至可以分层自定义 scope。 这意味着您可以为您的自定义 scope 分配子 scope。 然后选择仅清除子 scope 或清除父 scope(这将确保也清除任何子 scope)。
final class SignUpScope: ProviderScope {
let children: [any ProviderScope] = [SomeChildSignUpScope()]
}
final class SomeChildSignUpScope: ProviderScope {
let children: [any ProviderScope] = []
}
let someChildSignUpProvider = Provider(scope: SomeChildSignUpScope()) { _ in
return SomeChildSignUp()
}
// Clears all cached instances for SomeChildSignUpScope and SignUpScope
pod.clearCachedInstances(forScope: SignUpScope())
// Clears all cached instances for SomeChildSignUpScope
pod.clearCachedInstances(forScope: SomeChildSignUpScope())
关于 Providers
的最后一点注意事项
您可以为同一类型定义多个 provider。 这意味着您可以例如拥有 2 个 Providers
,它们都返回 String
类型。
并且这两个 provider 将独立存在,并拥有自己的缓存和生命周期。
let myAppTitleProvider = Provider { _ in
return "My cool app"
}
let myAppDescriptionProvider = Provider { _ in
return "This is a really exciting and cool app"
}
let string1 = pod.resolve(myAppTitleProvider) // <-- "My cool app"
let string2 = pod.resolve(myAppDescriptionProvider) // <-- "This is a really exciting and cool app"
pod 的主要任务是让您解析 provider 以获取一些实例。 💃🪩🕺
在您应用程序的某个地方,您需要通过执行以下操作来定义您的 pod
import SwiftiePod
...
let pod = SwiftiePod()
这将是您在整个应用程序中使用的 pod。
除了解析 provider 之外,pod 还有 2 个其他功能。 覆盖 provider 和清除缓存实例。
Providers
在编译时定义。 但有时覆盖/更改 Provider
的行为并更改其构建方式可能很有用。 例如,如果您想在测试期间或在 UI 预览期间提供模拟实例。
pod
为此提供了 overrideProvider(...)
。 您传入要覆盖的 provider,然后传入您想要使用的新构建方法。 如下所示
pod.overrideProvider(myCoolServiceProvider) { _ in
return MockMyCoolService()
}
现在,每次您调用 pod.resolve(myCoolServiceProvider)
时,您都将获得 MockMyCoolService
而不是真正的服务。
当您想要删除给定 Provider
的覆盖时,您可以调用 pod.removeOverrideProvider(...)
。 这将有效地删除覆盖,并且 provider 将再次返回原始实例。
从被覆盖的 provider 获取的实例也可以被缓存。 默认情况下,覆盖将使用与您覆盖的 provider 相同的 Scope
。 但是您可以更改为使覆盖具有自己的 Scope
。 为此,只需在调用 overrideProvider(...)
时传递一个 scope
参数即可。
如果您的 Provider 具有不是 AlwaysCreateNewScope
的 Scope
,那么您的 Provider
的实例将被缓存在 pod
中。 这意味着您第一次调用 pod.resolve(...)
时,它将运行构建器并创建一个新实例。 但是,当您第二次调用 pod.resolve(...)
时,它将只返回缓存的实例。
有时您可能想要清除这些缓存,例如,如果某些实例应仅在应用程序的给定流程(如注册流程)期间处于活动状态。 然后,您可以为所有这些 Providers
分配一个 SignUpFlowScope
,当您完成注册流程时,您可以调用 pod.clearCachedInstances(forScope: SignUpFlowScope())
。
这将确保所有缓存的(具有 SignUpFlowScope 的)实例都从缓存中清除。 因此,下次用户进入注册流程时,所有这些实例都将被重新创建并“干净”。
注意
使用 SingletonScope
的 Provider 将忽略 clearCachedInstances
,这意味着这些实例永远不会被清除。 它们在您的 pod
的生命周期内被缓存。
如果您发现错误、想要请求新功能或以任何其他方式做出贡献,请打开一个 issue 或提交 pull request。
根据 MIT 许可证分发。