【原】iOS下KVO使用過程當中的陷阱

KVO,全稱爲Key-Value Observing,是iOS中的一種設計模式,用於檢測對象的某些屬性的實時變化狀況並做出響應。網上廣爲流傳普及的一個例子是利用KVO檢測股票價格的變更,例如這裏。這個例子做爲掃盲入門仍是能夠的,可是當應用場景比較複雜時,裏面的一些細節仍是須要改進的,裏面有多個地方存在crash的危險。本文旨在逐步遞進深刻地探討出一種目前比較健壯穩定的KVO實現方案,彌補網上大部分教程的不足!編程

首先,假設咱們的目標是在一個UITableViewController內對tableview的contentOffset進行實時監測,很容易地使用KVO來實現爲。設計模式

在初始化方法中加入:函數

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

在dealloc中移除KVO監聽:spa

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

添加默認的響應回調方法:.net

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context
{
       [self doSomethingWhenContentOffsetChanges];
}

好了,KVO實現就到此完美結束了,拜拜。。。開個玩笑,確定沒這麼簡單的,這樣的代碼太粗糙了,當你在controller中添加多個KVO時,全部的回調都是走同上述函數,那就必須對觸發回調函數的來源進行判斷。判斷以下:設計

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context
{
    if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
       [self doSomethingWhenContentOffsetChanges];
} }

你覺得這樣就結束了嗎?答案是否認的!咱們假設當前類(在例子中爲UITableViewController)還有父類,而且父類也有本身綁定了一些其餘KVO呢?咱們看到,上述回調函數體中只有一個判斷,若是這個if不成立,此次KVO事件的觸發就會到此中斷了。但事實上,若當前類沒法捕捉到這個KVO,那頗有多是在他的superClass,或者super-superClass...中,上述處理砍斷了這個鏈。合理的處理方式應該是這樣的:server

- (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]; 
}
}

 

這樣就結束了嗎?答案仍舊是否認的。潛在的問題有可能出如今dealloc中對KVO的註銷上。KVO的一種缺陷(其實不能稱爲缺陷,應該稱爲特性)是,當對同一個keypath進行兩次removeObserver時會致使程序crash,這種狀況經常出如今父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的狀況下。不要覺得這種狀況不多出現!當你封裝framework開源給別人用或者多人協做開發時是有可能出現的,並且這種crash很難發現。不知道你發現沒,目前的代碼中context字段都是nil,那可否利用該字段來標識出到底kvo是superClass註冊的,仍是self註冊的?對象

回答是能夠的。咱們能夠分別在父類以及本類中定義各自的context字符串,好比在本類中定義context爲@"ThisIsMyKVOContextNotSuper";而後在dealloc中remove observer時指定移除的自身添加的observer。這樣iOS就能知道移除的是本身的kvo,而不是父類中的kvo,避免二次remove形成crash。blog

 

=======================================================教程

原創文章,轉載請註明 編程小翁@博客園,郵件zilin_weng@163.com,歡迎各位與我在C/C++/Objective-C/機器視覺等領域展開交流!

 =======================================================

相關文章
相關標籤/搜索