8.1: kvo 與 kvc 展開
? ? ?1:KVO
? ? ? ? ? ? KVO(Key-Value-Observing)鍵值觀察,其技術原理就是通過 isa waizzle 技術添加被觀察對象中間類,并重新寫相應的方法來監聽鍵值變化。當被觀察的對象屬性被修改后,則對象回接收到通知,即每次指定的被觀察對象的屬性被修改后,kvo就會自動通知相應的觀察者。
? ? ? ? ? ? isa swizzle 不同于method swizzle,其交換的是isa,對象的isa 指針式定義了它的類,所以ISA swizzling 指修改對象所指向的類,KVO則是使用該技術實現的,還有zombie objects檢測也用到了該技術,而method swizzle交換的是method
? ? ? ? 2:KVO引起的crash 情況如下
? ? ? ? ? ? 2.1* observer 已銷毀,但是未及時移除監聽;
? ? ? ? ? ? 2.2* addObserver 與 removeObserver 不匹配
? ? ? ? ? ? ? ? 1:移除了未注冊的觀察者,但是未及時移除監聽
? ? ? ? ? ? ? ? 2:重復移除多次,移除次數多于添加次數,導致崩潰。
? ? ? ? ? ? ? ? 3:重復添加多次,雖然不會崩潰,但是發生改變時,也同時會被觀察多次。
? ? ? ? ? ? 2.3*添加了觀察者,但未實現observerValueForKeyPath:ofObject:change:context: 方法,導致崩潰.
? ? ? ? ? ? 2.4*添加或移除時 keypath == nil ,導致崩潰.
? ? ? ? 通過如上場景就可以發現其實kvo 崩潰的主要原因是觀察者管理混亂,特別是觀察者關系復雜時,開發者容易導致混亂。
? ? ? ? ? ? 如下圖所示:
? ? ? ? 那如何管理呢? 既然觀察者都是開發者來管理,由人來管理必然會出現失誤的時候,那我們是否能通過一個代理對象來管理?
? ? ? ? ? ? 答案:yes!
? ? ? ? 3:具體實現如下:
? ? ? ? ? ? 1:通過Method Swizzle方法調配交換KVO相應的方法到NSObject基類,如下:
? ? ? ? ? ? 2: 然后在觀察者和被觀察者之間建立一個 KVODelegate 對象,
? ? ? ? ? ? ? ? 兩者之間通過 KVODelegate 對象 建立聯系。然后在添加和移除操作時,
? ? ? ? ? ? ? ? 將 KVO 的相關信息例如 observer、keyPath、options、context 保存為 KVOInfo 對象,并添加到 KVODelegate 對象 中對應 的 關系哈希表 中,對應原有的添加觀察者。
? ? ? ? ? ? 3: 在添加和移除操作的時候,
? ? ? ? ? ? ? ? 利用 KVODelegate 對象 做轉發,
? ? ? ? ? ? ? ? 把真正的觀察者變為 KVODelegate 對象,
? ? ? ? ? ? ? ? 而當被觀察者的特定屬性發生了改變(會被調用到observeValueForKeyPath:ofObject),
? ? ? ? ? ? ? ? 再由 KVODelegate 對象分發到原有的觀察者上。
? ? ? ? ? ? 4:為了避免被觀察者提前被釋放,
? ? ? ? ? ? ? ? 被觀察者在 dealloc 時仍然注冊著 KVO 導致崩潰。
? ? ? ? ? ? ? ? BayMax 系統還利用 Method Swizzling 實現了自定義的 dealloc,
? ? ? ? ? ? ? ? 在系統 dealloc 調用之前,將多余的觀察者移除掉。
? 8.1.2:KVC
? ? ? KVC(Key Value Coding)鍵值編碼,提供一種機制來間接訪問對象的屬性,而不是通過Setter/Getter方法進行訪問。
? ? ? 通常導致崩潰的原因不外乎鍵值設置不正確,如下:
? ? ? ? ? 1. key 不是對象的屬性
? ? ? ? ? 2. keyPath 不正確
? ? ? ? ? 3. value 為 nil,為非對象設值
? ? ? ? ? 4. key 為 nil
? ? ? 那如何防護呢,熟悉KVC機制的同學肯定清楚:runtime提供了相應的補救措施來避免應用崩潰,包括如下:
? ? ? ? ? ? setValue:forKey: 找不到相應的key會調用 setValue: forUndefinedKey: 方法;
? ? ? ? ? ? valueForKey: 找不到相應的keyPath會調用 valueForUndefinedKey: 方法;
? ? ? ? ? ? setValue:forKey:添加value為nil方法,會調用setNilValueForKey方法來避免;
? ? ? 因此,針對上面崩潰的前3中場景,就可以通過分辨實現上述三種方法來避免,但對于key為nil的情況該如何防護呢?
? ? ? ? ? ? 這里直接告訴答案:毅然是通過熟悉的Method Swizzle來替換原有的
? ? ? ? ? ? setValue:forKey:方法,
? ? ? ? ? ? 并判斷傳入的key是否為nil。具體代碼如下: