一个完全并发安全的 Swift 通用配置和依赖注入解决方案,大致受到 https://www.avanderlee.com/swift/dependency-injection/ 的启发。
声明一个服务
public protocol MyService : Sendable { ... }
public final class DefaultMyService : MyService { ... }
extension ConfKeys {
#declareServiceKey("myService", MyService.self, defaultValue: DefaultMyService())
}
/* You can have as many injected instances of your service as you want by declaring more keys for it. */
声明一个通过工厂实例化的服务
extension ConfKeys {
#declareServiceFactoryKey("myServiceFactory", MyService.self, defaultValue: { DefaultMyService() })
}
使用它
struct ServiceClient {
@InjectedConf(\.myService) var myService: MyService
@InjectedConf(\.myServiceFactory) var myServiceViaFactory: MyService
func myFunction() {
/* The myService variable will always be up-to-date. */
myService.doAmazingStuff(...)
/* Using the default definition set above for myServiceFactory, myServiceViaFactory will always be a new instance. */
myServiceViaFactory.doAmazingStuff(...)
}
}
更改注入的实例
func myAppInit() {
Conf.setRootValue(newService, for: \.myService)
}
/* Declare the configuration key. */
extension ConfKeys {
#declareConfKey("myConf", Int.self, defaultValue: 42)
}
/* Use it in your code. */
if Conf[\.myConf] == 42 {...}
/* Optionally you can define an internal convenience on Conf for easier access. */
internal extension Conf {
static var myConf: Int {Conf[\.myConf]}
}
自动注入服务是指可以使用 @InjectedConf
而无需在属性包装器初始化程序中指定键路径的服务。
当您的服务有一个“主要”实例,并且每次定义服务变量时都指定键路径是多余的时候,这非常有用。
对于这些服务,您可以使用 AutoInjectable
协议
public final class MyService { ... }
extension ConfKeys {
#declareServiceKey("myService"/* or nil if you don’t need the ConfKeys keypath */, MyService.self, "MyServiceKey", defaultValue: DefaultMyService())
}
extension MyService : AutoInjectable {
public typealias AutoInjectionKey = ConfKeys.MyServiceKey
}
现在可以像这样获取服务实例
struct ServiceClient {
@InjectedConf()
var myService: MyService
...
}
请注意,协议类型的服务不能是自动注入的。
对于 @MainActor
服务,您必须使用 ConfKeyMainActor
和 AutoInjectableMainActor
协议,而不是它们非 MainActor
的等价物。该宏有一个参数,用于传递用于声明配置或服务的全局 actor。
extension ConfKeys {
#declareConfKey("myConf", Int.self, on: MainActor.self, defaultValue: 42)
}
对于在其他全局(或非全局)actor上隔离的服务,您需要做更多的工作。