投稿文章,作者:keymon
序言
前幾天公交車上看了一篇百度大神的關於 KVO 探索的博客。我實地驗證了一下子,也遇到了好多問題,一番各種查閱資料之後,決定總結分享一下,供各位看官指點哈~
KVO的原理
下面的原理仔細品嘗喔~多讀幾遍就可以理解了,當然理解不了就按我說的來點 KVO 的代碼,最後腫能理解
1.當一個 object(對象) 有觀察者時候,動態創建這個 object(對象) 的類的子類
2.對於每個被觀察的 property(屬性),重寫其 setter 方法
3.在重寫的 setter 方法中調用 -willChangeValueForKey: 和 -didChangeValueForKey: 通知觀察者
4.當一個 property(屬性) 沒有觀察者時,刪除重寫的方法
5.當沒有 observer(觀察者) 觀察任何一個 property(屬性) 時,刪除動態創建的子類
Demo 驗證
Demo簡單到要死,覺著扔一張圖就看的明白,比廢話一大堆簡單多了,有時候看文字是件晦澀的事情,還得一個字一個字的理解,所以此處扔圖,有想看Demo的點我穿越。
接下來看看 KVO 是怎麼動態創建子類的:
斷點1—>代碼和 Log 日志
斷點1-代碼
斷點1-Log
對比上述2張圖,我們在斷點1處,在控制台分別使用- class 和 object_getClass() 打印person對象的類和真實的類,下面的斷點2和斷點3都按此方法打印 Log日志。
斷點2—>代碼和 Log 日志
斷點2-代碼
斷點2-Log
斷點3—>代碼和 Log 日志
斷點3-代碼
斷點3-Log
瞧~,斷點2的 Log 日志信息突然冒出了一個
NSKVONotifying_HQMPerson,這是什麼鬼。。。
我們知道為一個對象addObsever時候,也就是被觀察時,
framework使用runtime動態創建了一個HQMPerson類的子類NSKVONotifying_HQMPerson,而為了不讓外部知道這一行為,
NSKVONotifying_HQMPerson重寫了-class方法返回之前的類,所以通過-class方法查看的類沒有變化,但是通過object_getClass()方式就會暴露出來發生了何種變化,因為這個object_getClass()返回的是這個對象的isa指針,isa指針指向的一定是這個對象所屬的類。如下圖:
isa 指向 xx
常見錯誤
1.錯誤1-remove觀察者
造成該崩潰信息的代碼片段如下:
崩潰信息的代碼片段
上述代碼是對 person 這個對象添加了監聽,而removeObserver方法卻是移除的self,顯然這是一個很低級的錯誤。
解決方法: 觀察誰,誰就應該移除也就是偷窺誰,誰就發毛,所以就該跑
2.錯誤2-屬性的值修改了的信息收到了,但是並沒有處理
其實這個很簡單就是你addObserver了,但是方法-observeValueForKeyPath:ofObject:change:context:卻沒有實現,這個算是最低級的了。。。
解決方法:
PS:只要你注冊了 KVO,這個方法就必須實現
3.錯誤3-添加和移除時候,context上下文不一致
代碼片段如下:
context上下文不一致
解決方法:
一般來說context都傳nil
4.錯誤4-致命性
說實話遇到這個錯誤,我還是真不知道從何入手(皆因對 KVO 的理解不夠深),先看出現這種崩潰的原始代碼:
原始代碼片段
只要運行,程序就會爽快的崩潰。。。看下我的注釋,然後在對比一下崩潰日志信息(HQMPerson 類的實例被釋放了,但是 KVO 中還有關於他的注冊信息)。
實際上,只要你明白 KVO 的知識:在添加觀察者的時候,觀察者對象與被觀察的屬性所屬的對象都不會被retain,然而在這些對象被釋放後,相關的監聽信息卻還存在,(ARC環境下)KVO做的處理是直接讓程序崩潰。
解決方法:
既然明白了這一點,我們就知道如何修改了(ARC 環境下),如下修改:
修改後代碼片段
尾
關於 KVO 的觸發方式-自動和手動,以及更深的底層探索待續喔。。。會出 xxx(二)呢(這部分得參考Apple的官方文檔,英文有壓力)