借鉴了SwiftUI中@Environment属性包装器的方案;属性包装器允许注入依赖项并减少实现端的代码混乱。不需要大的初始值设定项,并且仍然有可能覆盖测试的依赖项。属性包装器还可以明确注入哪些属性,这可以提高可读性。

Injection

简介

借鉴了SwiftUI中@Environment属性包装器的方案;属性包装器允许注入依赖项并减少实现端的代码混乱。不需要大的初始值设定项,并且仍然有可能覆盖测试的依赖项。属性包装器还可以明确注入哪些属性,这可以提高可读性。

用到的技术:静态下标、扩展和属性包装器

具体解读

从调用开始,倒序介绍调用栈,分析源码

Step1:使用

使用propertyWrappper创建了一个新的对象networkProvider,它是一个协议对象,所以只要是遵循NetworkProviding的实例都可以赋值给networkProvider,然后进一步注意propertyWrappper创建的key

struct DataController {
    @Injected(\.networkProvider) var networkProvider: NetworkProviding
    
    func performDataRequest() {
        networkProvider.requestData()
    }
    
    func printNetworkProvider() {
        print(networkProvider)
    }
}

注意.\networkProvider 是keyPath

Step2: 创建propertyWrapper

@propertyWrapper
struct Injected<T> {
    private let keyPath: WritableKeyPath<InjectedValues, T>
    var wrappedValue: T {
        get { InjectedValues[keyPath] }
        set { InjectedValues[keyPath] = newValue }
    }
    
    init(_ keyPath: WritableKeyPath<InjectedValues, T>) {
        self.keyPath = keyPath
    }
}

初始化方法传入的参数是keyPath,这个keyPath会到放wrappedValue中,进一步,开始访问InjectionValues

Step3:InjectedValues

/// Provides access to injected dependencies.
struct InjectedValues {
    
    /// This is only used as an accessor to the computed properties within extensions of `InjectedValues`.
    private static var current = InjectedValues()
    
    /// A static subscript for updating the `currentValue` of `InjectionKey` instances.
    static subscript<K>(key: K.Type) -> K.Value where K : InjectionKey {
        get { key.currentValue }
        set { key.currentValue = newValue }
    }
    
    /// A static subscript accessor for updating and references dependencies directly.
    static subscript<T>(_ keyPath: WritableKeyPath<InjectedValues, T>) -> T {
        get { current[keyPath: keyPath] }
        set { current[keyPath: keyPath] = newValue }
    }
}

propertyWrapper中wrappedValue,InjectedValues[keyPath],调用InjectedValues中static subscript<T>(_ keyPath: WritableKeyPath<InjectedValues, T>) -> T,即第二个静态方法,

这个静态方法中,将会使用InjectedValues其内部的私有静态属性current,它是一个InjectedValues结构体实例;

进一步使用keyPath进行读取和设置,这个keyPath就是@Injected(\.networkProvider) var networkProvider: NetworkProviding 中对应的.networkProvider,而这个networkProvider,是在InjectedValues扩展中定义的

current[keyPath: keyPath] 执行时,会调用第一个静态方法static subscript<K>(key: K.Type) -> K.Value whereK : InjectionKey,因为keyPath是遵循

//InjectedValues中定义新的计算属性
extension InjectedValues {
    var networkProvider: NetworkProviding {
        get { Self[NetworkProviderKey.self] }
        set { Self[NetworkProviderKey.self] = newValue }
    }
    //suscript是static静态方法,需要通过类型本身调用,Self代表他所出现范围的类型别名
    //NetworkProviderKey.self]代表NetworkProviderKey类型的元类型(meta-type),就是类型的类型,因为后面要使用类型的静态(static)方法,不能是类型实例,所以需要传元类型
    
}

//InjectionKey的实现
private struct NetworkProviderKey: InjectionKey {
    static var currentValue: NetworkProviding = NetworkProvider()
}

其中NetworkProviderKey是遵循InjectionKey的一个实现,内部实现了currentValue,注意currentValue的定义,遵循NetworkProviding,赋值是NetworkProvider(),它是遵循NetworkProviding协议的一个实例,currentValue起到的作用是针对NetworkProviderKey的默认值,也就是外部使用时,不设置NetworkProviderKey时,默认是NetworkProvider();

然后Self[NetworkProviderKey.self],会调用到InjectedValues中的static subscript<K>(key: K.Type) -> K.ValuewhereK : InjectionKey

一次调用流程图

dataController.networkProvider = NetworkProvider()

Injected init(_ keyPath: WritableKeyPath<InjectedValues, T>)

var wrappedValue: T

InjectedValues static subscript(_ keyPath: WritableKeyPath<InjectedValues, T>) -> T

var networkProvider: NetworkProviding

InjectedValues static subscript(key: K.Type) -> K.ValuewhereK : InjectionKey

current

总结

在父类创建了包含@Injected的propertyWrapper属性,且父类没有销毁的情况下,子类和父类都可以维护属性的值,完成(1)依赖注入和替换 (2)值传递

源码

https://github.com/kingnight/Injection

参考原文链接

https://www.avanderlee.com/swift/dependency-injection/?utm_source=swiftlee&utm_medium=swiftlee_weekly&utm_campaign=issue_72