手動實現KVO

Jerry4me發表於2016-09-10

我的Github地址 : Jerry4me, 本文章的demo連結 : JRCustomKVODemo

 


前言

KVO(Key-Value Observing, 鍵值觀察), KVO的實現也依賴於runtime. 當你對一個物件進行觀察時, 系統會動態建立一個類繼承自原類, 然後重寫被觀察屬性的setter方法. 然後重寫的setter方法會負責在呼叫原setter方法前後通知觀察者. KVO還會修改原物件的isa指標指向這個新類.

我們知道, 物件是通過isa指標去查詢自己是屬於哪個類, 並去所在類的方法列表中查詢方法的, 所以這個時候這個物件就自然地變成了新類的例項物件.

不僅如此, Apple還重寫了原類的- class方法, 檢視欺騙我們, 這個類沒有變, 還是原來的那個類(偷龍轉鳳). 只要我們懂得Runtime的原理, 這一切都只是掩耳盜鈴罷了.

以下實現是參考Glow 技術團隊部落格的文章進行修改而成, 主要目的是加深對runtime的理解, 大家看完後不妨自己動手實現以下, 學而時習之, 不亦樂乎


KVO的缺陷

Apple給我們提供的KVO不能通過block來回撥處理, 只能通過下面這個方法來處理, 如果監聽的屬性多了, 或者監聽了多個物件的屬性, 那麼這裡就痛苦了, 要一直判斷判斷if else if else….多麻煩啊, 說實話我也不懂為什麼Apple不提供多一個傳block引數的方法

那麼, 既然Apple沒有幫我們實現, 那我們就手動實現一個唄, 先看下我們最終目標是什麼樣的 :

簡簡單單就能讓observer監聽object的兩個屬性, 並且監聽屬性改變後的回撥就在對應的callback下, 清晰明瞭, 何不快哉! Talk is cheep, show you the code!


首先, 我們為NSObject新增一個分類

NSObject+jr_KVO.h

新增觀察者

jr_addObserver方法裡我們需要做什麼呢?

  1. 檢查物件是否存在該屬性的setter方法, 沒有的話我們就做什麼都白搭了, 既然別人都不允許你修改值了, 那也就不存在監聽值改變的事了
  2. 檢查自己(self)是不是一個kvo_class(如果該物件不是第一次被監聽屬性, 那麼它就是kvo_class, 反之則是原class), 如果是, 則跳過這一步; 如果不是, 則要修改self的類(origin_class -> kvo_class)
  3. 經過第二部, 到了這裡已經100%確定self是kvo_class的物件了, 那麼我們現在就要重寫kvo_class物件的對應屬性的setter方法
  4. 最後, 將觀察者物件(observer), 監聽的屬性(key), 值改變時的回撥block(callback), 用一個模型(JRObserverInfo)存進來, 然後利用關聯物件維護self的一個陣列(NSMutableArray *)

這段程式碼還有幾個方法, 我們下面一一解釋…

首先, setterForGettergetterForSetter, 這兩個方法好辦. 第一個就是根據getter方法名獲得對應的setter方法名, 第二個就是根據setter方法名獲得對應的getter方法名

這裡需要注意的是, 首字母轉換成大寫這一項, 不能直接呼叫NSString的capitalizedString方法, 因為該方法返回的是除了首字母大寫之外其他字母全部小寫的字串.

然後, 接下來就是jr_KVOClassWithOriginalClassName:方法了

這個方法還是很直觀明瞭的, 可能不太明白的是為什麼要為kvo_class這個類重寫class方法呢? 原因是我們要把這個kvo_class隱藏掉, 讓別人覺得自己的類沒有發生過任何改變, 以前是Person, 新增觀察者之後還是Person, 而不是KVO_Person.
這個jr_class實現也很簡單.

最後, 重頭戲來了, 那就是重寫kvo_class的setter方法! Observing也正正是在這裡體現出來的.

臥槽, struct objc_super是什麼玩意, 臥槽, ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);這一大串又是什麼玩意???

1862021-1a8422453f88e0cc
?????

首先, 我們來看看objc_msgSendobjc_msgSendSuper的區別 :

那麼, 很顯然, 我們呼叫objc_msgSendSuper的時候, 第一個引數已經不一樣了, 他接受的是一個指向結構體的指標, 於是才有了我們上面廢力氣建立的一個看似無用結構體

另外, 呼叫objc_msgSend總是需要做方法的型別強轉,


移除監聽者

移除監聽者就easy easy easy太多了, 直接上程式碼吧

相信不用註釋大家也能看懂, 大家記得在物件- dealloc方法中呼叫該方法移除監聽者就OK了, 否則有可能報野指標錯誤, 訪問壞記憶體.


監聽者資訊

JRObserverInfo是個什麼模型呢? 這裡告訴大家…


執行展示

這裡我就簡單做個展示, 下面的textLabel監聽上面colorView背景色的改變, 點選button, 改變上面colorView的顏色, 然後textLabel輸出colorView的當前色

1862021-8405fc669abc9eb1
執行結果

demo可在JRCustomKVODemo這裡下載, 同時歡迎大家關注我的Github, 覺得有幫助的話還請給個star~~


參考 :
如何自己動手實現KVO

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

手動實現KVO 手動實現KVO

相關文章