入門篇html
KVO是什麼?前端
Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.ios
KVO 是 Objective-C 對觀察者模式(Observer Pattern)的實現。也是 Cocoa Binding 的基礎。當被觀察對象的某個屬性發生更改時,觀察者對象會得到通知,而且得知此時這個屬性的具體值。面試
有點繞口。簡單點說,好比你監視你老婆,你老婆跟老王跑了,你會當即知道你老婆是跟老王跑了,而且也會知道上次是跟張三跑的。微信
KVO如何使用?前端工程師
最好的使用入門指南:Introduction to Key-Value Observing Programming Guide架構
基礎篇app
KVO實現原理 – In Apple Wayide
蘋果的實現方式用猿哥這張經典的圖理解起來還不錯,可是可能不太適合新手。函數
KVO的實現是經典的添加中間層的思想,雖然用了isa-swizzling,可是很符合邏輯,並無什麼黑魔法。咱們來個生活中的實例講解下:
咱們假設有一個叫Foo的女孩,有一個叫Bar的小夥。最初他倆素未謀面:
有一天小夥Bar去網吧打擼,碰巧Foo也坐在旁邊擼。Bar以爲Foo很漂亮,感受喜歡上了Foo。因而他邀請Foo一塊兒擼並趁機要了Foo妹子的微信,並說之後帶你飛。其實Bar是爲了觀察Foo妹子的票圈,隨時點贊評論,說不定還有機會趁機表白。因而世界由於Bar的豔遇而運行了一段代碼:
1 2 3 4 5 6 7 8 9 10 |
|
而後在地球上,他倆關係就變了:(畫風陡變,前方高能= =)
Foo由於被Bar關注,因此她必定再也不是原來那個美女,而是和之前不同的美女(note:仍是繼承了原來美女的屬性和方法)。她仍是擁有原來的容顏和生活習慣,可是如今她在微信上的一舉一動都會被Bar所察覺到了。
如今女孩回了家,發了一條微信,內容是「哎,好想找個伴」。在最後
1 |
|
執行結束的時候小夥就會看到這條消息。
你看KVO的實現方式不是很符合邏輯嗎。
有興趣深刻蘋果實現方式的同窗就看顧神這篇文章吧《如何本身動手實現 KVO》。他的思路是比較官方化的,只是回調用的block。
這裏介紹他代碼裏runtime中關鍵點實現函數原型:
1.新類被建立並繼承自父類:
1 |
|
2.在新類上重寫setXXX方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
3.女孩Foo的isa指向新類
1 |
|
進階篇
假如面試官問你KVO是怎麼實現的,你如上回答能夠勉強過關,可是你說出下面的點那就能讓他十分滿意了,說什麼?吐槽。
KVO的缺點
AFNetworking做者Mattt Thompson在《Key-Value Observing》中說:
Ask anyone who’s been around the NSBlock a few times: Key-Value Observing has the worst API in all of Cocoa.
缺點1:全部的observe處理都放在一個方法裏
假設咱們要觀察一個tableView的contentSize,看上去使用KVO是個不錯的選擇,由於沒有其餘方法去獲知這個屬性被改變。Ok,首先,添加觀察者:
1 |
|
很好,實現屬性被改變的響應:
1 2 3 |
|
完成!Just kidding.考慮到該方法中可能有其餘的observe事務,因此咱們要這樣修改:
1 2 3 4 5 |
|
若是KVO處理的事務繁多,就會形成該方法代碼特別長,而且十分不優雅,咱們還沒轍。
缺點2:嚴重依賴於string
KVO嚴重依賴於string,換句話說若是KVO的keyPath若是錯誤編譯器沒法查出,好比咱們可能會把@「contentSize」寫成@「contentsize」,而後你就只能傻傻的找半個小時bug。
所以咱們不得不使用NSStringFromSelector(@selector(contentSize))編譯器就能判斷是否存在這個屬性,而且咱們也好修改,可是代碼這麼長,逼死強迫症。
並且,咱們也不能用KVO觀察多值路徑,好比咱們觀察一個viewController而且想得到scrollView的contentOffset,咱們是不能用這樣的keyPath:scrollview.contentOffset來獲得的。
所以上面的代碼又得變成這樣:
1 2 3 4 5 |
|
缺點3:須要本身處理superclass的observe事務
對於Objective-C,不少時候runtime系統都會自動幫忙處理superClass的方法,好比dealloc。在ARC下,調用子類的dealloc方法,runtime會自動調用父類的dealloc。可是KVO不會,或者說是不行,由於runtime並不知道父類有沒有observe事務,而且因爲它是用協議實現的,一次只能觸發一個observeValueForKeyPath:ofObject:change:context:方法,因此若是子類和父類都有observe事務,咱們要這樣作:
1 2 3 4 5 6 7 8 |
|
缺點4:屢次相同KVO的removeObserve會致使crash
寫過KVO代碼的人都知道,對同一個對象執行兩次removeObserver操做會致使程序crash。
在同一個文件中執行兩次相同的removeObserver屬於粗心,比較容易debug出來;可是跨文件執行兩次相同的removeObserver就不是那麼容易發現了。
咱們通常會在dealloc中進行removeObserver操做(這也是Apple所推薦的)。
思考這樣一個場景:一個JZTableView繼承自UITableview,他們都監聽了tableView的contentSize屬性,那麼UItableview和JZTableView的dealloc都會有這句代碼:
1 2 3 4 5 |
|
那麼當JZTableview的對象釋放時,他和她父類的dealloc都會被調用,兩次相同的removeObserve,天然就Crash了。
還有不少點,不過說出上面4個就差很少了。
那麼如何改進?
首先列舉缺點:
全部的observe處理都放在一個方法裏
嚴重依賴於string
須要本身處理superclass的observe事務
屢次相同KVO的removeObserve會致使crash
而後直接上肖哥的一篇博客:一句代碼,更加優雅的調用KVO和通知
note:如下內容有興趣讀源碼的同窗能夠看看,不然直接看下一小節吧。
他的代碼架構圖以下:
每一個被觀察的對象有一個本身的字典,key爲被觀察的keyPath,存的值爲一個真正的觀察者對象Target,即dict[keyPath] = target。一個keyPath對應一個target,每一個target有一個KVOBlockSet存放該keyPath下的全部Block。
1 2 3 4 |
|
缺陷處理結果分析:
全部的observe處理都放在一個方法裏。解決。由於回調被分散成塊了,再也不集中。顧神也是用的塊實現的回調。可是用塊也不是很統一,但我的以爲仍是比原生的好點。
嚴重依賴於string。未解決。
須要本身處理superclass的observe事務。解決。全部執行了xw_addObserverBlockForKeyPath的塊都會被放入KVOBlockSet,對應的keyPath值被改變時會回調對應的全部block,不存在調用super的問題。
屢次相同KVO的removeObserve會致使crash。做者用的hook dealloc調劑remove方法達到自動移除功能,目前來看邏輯沒問題。
THE END
上文說道Foo妹紙發了一條微信「哎,好想找個伴」。Bar小夥見時機成熟,微信上大膽表白「你看我風流倜儻,隨手一敲就是一個十位數內計算器App,你看我作你的鴛鴦咋樣?」,Foo妹紙沉默了10分鐘回答道:「對不起,我可能更喜歡前端工程師」。
1 2 3 |
|
原文地址:http://www.cocoachina.com/ios/20160803/17265.html