KVO的底層實現(xiàn)原理是什么?KVO有哪些坑需要避免?如何安全地移除KVO觀察者?

KVO(Key-Value Observing)的底層實現(xiàn)原理

KVO(鍵值觀察)是一種觀察者模式的實現(xiàn),它允許對象觀察其他對象的屬性變化,并在屬性變化時接收通知。KVO的使用非常方便,但它背后的實現(xiàn)相當復雜,主要依賴于Objective-C的動態(tài)特性。

KVO 的底層實現(xiàn)

  1. 動態(tài)創(chuàng)建子類:當你第一次向?qū)ο筇砑佑^察者時,KVO會動態(tài)創(chuàng)建該對象所屬類的一個子類,并將該對象的類指針(isa指針)指向這個新子類。這個子類通常命名為NSKVONotifying_<ClassName>
  2. 方法重寫:在這個動態(tài)生成的子類中,KVO會重寫被觀察屬性的setter方法。例如,如果你觀察了name屬性,KVO會在NSKVONotifying_<ClassName>類中重寫setName:方法。
  3. 通知機制:
  • 在重寫的setter方法中,KVO會插入一些通知代碼。具體流程是:
    • 調(diào)用willChangeValueForKey:方法,告知系統(tǒng)即將發(fā)生屬性值的改變。
    • 調(diào)用原來的setter方法,更新屬性值。
    • 調(diào)用didChangeValueForKey:方法,觸發(fā)通知。這個方法會調(diào)用觀察者的observeValueForKeyPath:ofObject:change:context:方法,通知觀察者屬性值已經(jīng)改變。
  1. 依賴關(guān)系:如果屬性A依賴于屬性B的值變化,KVO也會通過+keyPathsForValuesAffecting<Key>方法自動建立這種依賴關(guān)系,確保屬性A的變化也能觸發(fā)通知。
  2. KVO的類驗證:當你調(diào)用[self class]時,如果該對象已經(jīng)被KVO動態(tài)生成的子類替換過isa指針,[self class]依然會返回原始類,而不是NSKVONotifying_<ClassName>。這是通過動態(tài)子類的class方法來實現(xiàn)的,它重寫了class方法以返回正確的類信息。

KVO的緩存機制

為了提高性能,KVO使用了緩存機制,當你第一次注冊觀察者時,系統(tǒng)會創(chuàng)建必要的結(jié)構(gòu)來處理觀察者的通知,并緩存這些信息。在屬性變化時,系統(tǒng)可以直接使用緩存的信息,減少了性能開銷。

KVO 的坑及避免方法

盡管KVO功能強大,但它在使用時有許多潛在的陷阱需要注意:

  1. 移除觀察者:
    • 自動移除 vs 手動移除:在iOS 9之前,你需要手動移除觀察者,否則會導致崩潰(特別是在觀察者已經(jīng)被釋放的情況下)。從iOS 9開始,Apple引入了自動移除觀察者的機制,但這只適用于ARC(自動引用計數(shù))環(huán)境下。
    • 多次移除:在iOS 9之前,如果多次移除同一個觀察者,也會導致崩潰。為了避免這個問題,確保在移除觀察者前檢查觀察者是否已經(jīng)被移除。
  2. 觀察者的生命周期:
    • 野指針崩潰:如果觀察者對象被釋放后沒有及時移除KVO,那么在被觀察屬性發(fā)生變化時,KVO仍然會嘗試通知已經(jīng)釋放的觀察者,這會導致野指針崩潰。因此,在觀察者即將被銷毀時,一定要移除觀察者。
    • 未匹配的添加/移除:確保添加和移除觀察者的邏輯是對稱的,否則容易導致內(nèi)存泄漏或崩潰。
  3. 多線程問題:
    • 線程安全性:KVO不是線程安全的。如果多個線程同時對同一個屬性進行KVO操作,可能導致競態(tài)條件(race condition)和崩潰。為避免此問題,應確保對KVO操作的代碼在單一線程或在鎖定機制(例如@synchronized)下執(zhí)行。
  4. 回調(diào)機制:
    • KVO回調(diào)中的操作:在KVO的回調(diào)中,如果對被觀察對象的屬性再進行修改,會導致無限遞歸和棧溢出。因此在回調(diào)中應避免直接修改被觀察的屬性。
  5. 未記錄的行為:
    • 非NSObject對象的KVO:KVO機制在某些非NSObject類對象上可能無法工作,例如直接繼承自純C結(jié)構(gòu)體的對象。因為KVO依賴于Objective-C的runtime特性,這些對象可能不支持動態(tài)方法調(diào)度。

如何安全地移除KVO觀察者

為了安全地移除KVO觀察者,可以遵循以下幾種方式:

  1. 在合適的時機移除:
    最佳實踐是在對象的dealloc方法中移除KVO觀察者,確保在對象銷毀前不會再接收KVO通知。
- (void)dealloc {
    [self removeObserver:self forKeyPath:@"property"];
}
  1. 避免重復移除:
    使用try-catch塊或者判斷是否已經(jīng)添加觀察者來防止重復移除。
@try {
    [self removeObserver:self forKeyPath:@"property"];
} @catch (NSException *exception) {
    // Handle exception or simply log
}
  1. 自動移除(iOS 9 及以后):
    從iOS 9開始,如果你使用-removeObserver:forKeyPath:方法手動移除觀察者,即使對象已經(jīng)被銷毀,也不會導致崩潰。ARC也自動管理了部分觀察者的移除,但開發(fā)者應依然小心管理。
  2. 使用外部庫:
    使用第三方庫如FBKVOController等,它們?yōu)镵VO提供了更加安全和便捷的接口。FBKVOController自動處理觀察者的移除,避免手動管理KVO引起的各種潛在問題。

總結(jié)

KVO是一個強大的機制,可以用于觀察對象屬性的變化并響應這些變化。它的底層實現(xiàn)依賴于Objective-C的動態(tài)特性,包括動態(tài)創(chuàng)建子類、重寫setter方法等。盡管KVO提供了很大的靈活性,但也伴隨著許多潛在的坑,如多次移除觀察者、線程安全問題、回調(diào)中的無限遞歸等。通過謹慎管理觀察者的生命周期、在適當?shù)臅r機移除觀察者,以及使用更安全的KVO工具庫,開發(fā)者可以有效避免這些問題。

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

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

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