KVOController是目前OC上用的最多的KVO的第三方庫,facebook出品。有以下特點:
- 提供
block方式和selector方式,不需要實現(xiàn)observeValueForKeyPath:ofObject:change:context: - 不需要關(guān)心
context,keyPath等參數(shù)來判斷是哪個觀察者的事件 - 在某些情況下,不需要手動移除觀察者
本文主要探討以下內(nèi)容:
1. 類之間的引用關(guān)系
2. 什么情況可以自動移除觀察者,不需要開發(fā)者關(guān)心
3. KVOControllerNonRetaining
4. 實際開發(fā)中怎么用更方便
類之間的引用關(guān)系
如下圖所示:
- 調(diào)用者持有
KVOController,并調(diào)用KVOController中的observe:keyPath:options:block:方法注冊觀察者,并在block中處理自己的業(yè)務(wù) -
_FBKVOInfo記錄調(diào)用者的信息,并作為context上下文參數(shù)傳遞。當(dāng)收到系統(tǒng)回調(diào)時取出該上下文,并回調(diào)給調(diào)用者 -
_FBKVOSharedController是一個單例,真正調(diào)用系統(tǒng)addObserver的觀察者 -
NSObject+FBKVOController分類中提供兩個屬性,KVOController和KVOControllerNonRetaining
4.1 這兩個屬性都是strong引用,即調(diào)用者會強引用KVOController
4.2KVOController表示被觀察者也是強引用的
4.3KVOControllerNonRetaining表示被觀察者是弱引用

類引用關(guān)系.png
理想情況下的自動解除KVO
從上圖可以看到,KVOController唯一被強引用的地方就是self(observer)。所以,只要self對象被釋放,KVOController隨后就會被釋放。在KVOController的dealloc方法中,調(diào)用了unobserveAll,從而實現(xiàn)了自動移除所有觀察者的功能。
但現(xiàn)實總是殘酷的,總有可能不小心導(dǎo)致self對象釋放不了,如:
-
block中使用了self,導(dǎo)致block強引用self,此時調(diào)用者就無法自動釋放了 - 被觀察者是
self,此時KVOController的_objectInfosMap屬性中的key即為self,且是強引用的,所以調(diào)用者也無法釋放
KVOControllerNonRetaining
在上面的被觀察者是self的情況時,可能有人注意到了在 NSObject+FBKVOController分類中提供了一個屬性:KVOControllerNonRetaining。是不是可以用這個屬性就可以做到自動移除觀察者了呢?
答案是否定的,這種情況依然無法移除觀察者,而且會造成崩潰。
這是因為:
-
KVOControllerNonRetaining表示_objectInfosMap屬性中的key為弱引用。 - 此時
observer == observed == self - 當(dāng)
self被釋放時,KVOControllerNonRetaining會執(zhí)行dealloc中的unobserveAll,由于_objectInfosMap屬性中的key為弱引用,NSMapTable會自動清除該鍵值對,所以永遠(yuǎn)移除不了觀察者。而self對象,由于還依然存在著注冊的觀察者(_FBKVOSharedController),最終導(dǎo)致崩潰。
回歸實際
從我個人實際的開發(fā)角度而言,self通常既是我的觀察者,也是我的被觀察者,因為就算我觀察的不是類本身的屬性,也通常是我類的屬性的屬性。而此時我通常喜歡使用被觀察者是self的原因是OC提供的Keypath方式,通過FBKVOKeyPath宏可以方便的進(jìn)行點.操作,如FBKVOKeyPath(self.clock.date)。
所以FBKVOController對我而言,常用的方式是:
- 使用分類中的
KVOController屬性 - 調(diào)用帶
block的方法 - 手動移除觀察者