iOS中KVO的使用

KVO,全稱為Key-Value Observing,是iOS中的一種設(shè)計模式,用于檢測對象的某些屬性的實時變化情況并作出響應(yīng)。網(wǎng)上廣為流傳普及的一個例子是利用KVO檢測股票價格的變動,例如這里。這個例子作為掃盲入門還是可以的,但是當應(yīng)用場景比較復(fù)雜時,里面的一些細節(jié)還是需要改進的,里面有多個地方存在crash的危險。本文旨在逐步遞進深入地探討出一種目前比較健壯穩(wěn)定的KVO實現(xiàn)方案,彌補網(wǎng)上大部分教程的不足!

首先,假設(shè)我們的目標是在一個UITableViewController內(nèi)對tableview的contentOffset進行實時監(jiān)測,很容易地使用KVO來實現(xiàn)為。

在初始化方法中加入:

[_tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];

在dealloc中移除KVO監(jiān)聽:

[_tableView removeObserver:self forKeyPath:@"contentOffset" context:nil];

添加默認的響應(yīng)回調(diào)方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

? ? ? [self doSomethingWhenContentOffsetChanges];

}

好了,KVO實現(xiàn)就到此完美結(jié)束了,拜拜。。。開個玩笑,肯定沒這么簡單的,這樣的代碼太粗糙了,當你在controller中添加多個KVO時,所有的回調(diào)都是走同上述函數(shù),那就必須對觸發(fā)回調(diào)函數(shù)的來源進行判斷。判斷如下:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

? ? if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {

? ?? [self doSomethingWhenContentOffsetChanges];

} }

你以為這樣就結(jié)束了嗎?答案是否定的!我們假設(shè)當前類(在例子中為UITableViewController)還有父類,并且父類也有自己綁定了一些其他KVO呢?我們看到,上述回調(diào)函數(shù)體中只有一個判斷,如果這個if不成立,這次KVO事件的觸發(fā)就會到此中斷了。但事實上,若當前類無法捕捉到這個KVO,那很有可能是在他的superClass,或者super-superClass...中,上述處理砍斷了這個鏈。合理的處理方式應(yīng)該是這樣的:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

? ? if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {

? ? ? ? [self doSomethingWhenContentOffsetChanges];

} else {

? ? ? ? [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

} }

這樣就結(jié)束了嗎?答案仍舊是否定的。潛在的問題有可能出現(xiàn)在dealloc中對KVO的注銷上。KVO的一種缺陷(其實不能稱為缺陷,應(yīng)該稱為特性)是,當對同一個keypath進行兩次removeObserver時會導(dǎo)致程序crash,這種情況常常出現(xiàn)在父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的情況下。不要以為這種情況很少出現(xiàn)!當你封裝framework開源給別人用或者多人協(xié)作開發(fā)時是有可能出現(xiàn)的,而且這種crash很難發(fā)現(xiàn)。不知道你發(fā)現(xiàn)沒,目前的代碼中context字段都是nil,那能否利用該字段來標識出到底kvo是superClass注冊的,還是self注冊的?

回答是可以的。我們可以分別在父類以及本類中定義各自的context字符串,比如在本類中定義context為@"ThisIsMyKVOContextNotSuper";然后在dealloc中remove observer時指定移除的自身添加的observer。這樣iOS就能知道移除的是自己的kvo,而不是父類中的kvo,避免二次remove造成crash。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容