ReactorKit 經過擴展給實例添加實例變量

ReactorKit 中定義了 Reactor 協議,同時對 Reactor 協議進行了擴展。一個類型經過繼承 Reactor 協議,就得到了下面幾個屬性:api

  • action: ActionSubject
  • currentState: State
  • state: Observable
  • scheduler: Scheduler
  • disposeBag: DisposeBag 

如何經過協議進行擴展,來給繼承者添加實例變量呢?bash

// MARK: - Map Tables
private typealias AnyReactor = AnyObject

private enum MapTables {
    static let action = WeakMapTable<AnyReactor, AnyObject>()
    static let currentState = WeakMapTable<AnyReactor, Any>()
    static let state = WeakMapTable<AnyReactor, AnyObject>()
    static let disposeBag = WeakMapTable<AnyReactor, DisposeBag>()
    static let isStubEnabled = WeakMapTable<AnyReactor, Bool>()
    static let stub = WeakMapTable<AnyReactor, AnyObject>()
}
複製代碼

經過源碼查看,能夠發現有一個 MapTables 的枚舉類型,裏面定義了幾個靜態常量(static let),每一個靜態常量都對應了一個擴展的屬性,好比 action 靜態變量就是爲了給繼承 Reactor 協議的類型添加 action 屬性。ui

WeakMapTable 的實現

在 MapTables 中的靜態常量的類型爲 WeakMapTable。其中,定義了一個 dictionary,將類和擴展得來的屬性值進行對應,這樣就能夠記錄每一個類對應的擴展的屬性值。spa

WeakMapTable 管理的 dictionary 須要知足下面的條件:code

  • 條件 1:實例和 dictionary 中的 key 須要一一對應,才能保證在 dictionary 中設置、獲取時用到的 key 時同一個。
  • 條件 2:key 不能對被擴展的實例進行強引用,不然沒法釋放被擴展的實例。
  • 條件 3:被擴展的實例在釋放時,須要通知 dictionary,將相應的 key 進行刪除。

下面來看一下 WeakMapTable 是怎麼管理 dictionary 的。dictionary 的 key 的類型是 Weak。一個 Weak 實例對別擴展的類進行了弱引用,並保存了類實例的惟一標識的哈希值。繼承

// MARK: - Weak

private class Weak<T>: Hashable where T: AnyObject {
    private let objectHashValue: Int
    weak var object: T?
    
    init(_ object: T) {
        // 類實例的惟一標誌
        self.objectHashValue = ObjectIdentifier(object).hashValue
        self.object = object
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.objectHashValue)
    }
    
    static func == (lhs: Weak<T>, rhs: Weak<T>) -> Bool {
        return lhs.objectHashValue == rhs.objectHashValue
    }
}
複製代碼

這樣就知足了條件 1 和條件 2。ci

在設置 dictionary 中 key 對應的被擴展的屬性值時的方法以下:rem

func setValue(_ value: Value?, forKey key: Key) {
    let weakKey = Weak(key)
    
    self.lock.lock()
    defer {
        self.lock.unlock()
        if value != nil {
            self.installDeallocHook(to: key)
        }
    }
    
    if let value = value {
        self.dictionary[weakKey] = value
    } else {
        self.dictionary.removeValue(forKey: weakKey)
    }
}
複製代碼

其中調用了 installDeallocHook 方法,其中的參數爲被擴展的類,installDeallocHook 的實現以下:get

// MARK: Dealloc Hook

private var deallocHookKey: Void?

private func installDeallocHook(to key: Key) {
    let isInstalled = (objc_getAssociatedObject(key, &deallocHookKey) != nil)
    
    guard !isInstalled else { return }
    
    let weakKey = Weak(key)
    let hook = DeallocHook(handler: { [weak self] in
        self?.lock.lock()
        self?.dictionary.removeValue(forKey: weakKey)
        self?.lock.unlock()
    })

    objc_setAssociatedObject(key, &deallocHookKey, hook, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
複製代碼

installDeallocHook 方法就是將被擴展的實例關聯了一個 DeallocHook 實例變量。DeallocHook 初始化的 handler 是將 Dictionary 中被擴展的實例對應的 key 進行了刪除。源碼

DeallocHook 的定義以下:

// MARK: - DeallocHook

private class DeallocHook {
    private let handler: () -> Void
    
    init(handler: @escaping () -> Void) {
        self.handler = handler
    }
    
    deinit {
        self.handler()
    }
}
複製代碼

DeallocHook 實例做爲被擴展的實例的屬性。被擴展的實例在銷燬時,DeallocHook 天然也會銷燬,這時就會調用 handler。這樣就保證的條件 3。

總結

WeakMapTable 經過下面的方式來對實例擴展實例變量屬性:

  1. 定義一個 Dictionary 來保存給實例擴展的屬性值。
  2. Dictionary 的 key 由實例的惟一標識生成,value 爲擴展的屬性值。
  3. 給被擴展的實例關聯一個 DeallocHook 類,當 DeallocHook 類釋放時,移除 Dictionary 中對應的 key,從而達到對擴展得來的屬性值的釋放。
相關文章
相關標籤/搜索