KVO指南

入門篇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

蘋果的實現方式用猿哥這張經典的圖理解起來還不錯,可是可能不太適合新手。函數

6941baebjw1f6e3mjj45aj20qh0fvjsc.jpg

KVO的實現是經典的添加中間層的思想,雖然用了isa-swizzling,可是很符合邏輯,並無什麼黑魔法。咱們來個生活中的實例講解下:

咱們假設有一個叫Foo的女孩,有一個叫Bar的小夥。最初他倆素未謀面:

6941baebjw1f6e3mj77gdj20a90bt0sn.jpg

有一天小夥Bar去網吧打擼,碰巧Foo也坐在旁邊擼。Bar以爲Foo很漂亮,感受喜歡上了Foo。因而他邀請Foo一塊兒擼並趁機要了Foo妹子的微信,並說之後帶你飛。其實Bar是爲了觀察Foo妹子的票圈,隨時點贊評論,說不定還有機會趁機表白。因而世界由於Bar的豔遇而運行了一段代碼:

1

2

3

4

5

6

7

8

9

10

- (void)registerAsObserver {

    /*

     當girlFoo的wechat屬性改變時通知boyBar

     */

    [girlFoo addObserver:boyBar

             forKeyPath:@"wechat"

                 options:(NSKeyValueObservingOptionNew |

                            NSKeyValueObservingOptionOld)

                    context:NULL];

}

而後在地球上,他倆關係就變了:(畫風陡變,前方高能= =)

6941baebjw1f6e3miw3f7j20yg0jzgmr.jpg

Foo由於被Bar關注,因此她必定再也不是原來那個美女,而是和之前不同的美女(note:仍是繼承了原來美女的屬性和方法)。她仍是擁有原來的容顏和生活習慣,可是如今她在微信上的一舉一動都會被Bar所察覺到了。

如今女孩回了家,發了一條微信,內容是「哎,好想找個伴」。在最後

1

[self didChangeValueForKey:@"wechat"];

執行結束的時候小夥就會看到這條消息。

你看KVO的實現方式不是很符合邏輯嗎。

有興趣深刻蘋果實現方式的同窗就看顧神這篇文章吧《如何本身動手實現 KVO》。他的思路是比較官方化的,只是回調用的block。

這裏介紹他代碼裏runtime中關鍵點實現函數原型:

1.新類被建立並繼承自父類:

1

objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

2.在新類上重寫setXXX方法

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

3.女孩Foo的isa指向新類

1

Class object_setClass(id object, Class cls)

進階篇

假如面試官問你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

[_tableView addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

很好,實現屬性被改變的響應:

1

2

3

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    [self configureView];

}

完成!Just kidding.考慮到該方法中可能有其餘的observe事務,因此咱們要這樣修改:

1

2

3

4

5

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {

        [self configureView];

    }

}

若是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

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (object == _tableView && [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) {

        [self configureView];

    }

}

缺點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

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {

        [self configureView];

    else {

    // 若是咱們忘記這句,那麼父類不會收到通知

        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

    }

}

缺點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

- (void)dealloc {

     ...

    [_tableView removeObserver:self forKeyPath:@"contentSize" context:NULL];

    ...

}

那麼當JZTableview的對象釋放時,他和她父類的dealloc都會被調用,兩次相同的removeObserve,天然就Crash了。

還有不少點,不過說出上面4個就差很少了。

那麼如何改進?

首先列舉缺點:

  • 全部的observe處理都放在一個方法裏

  • 嚴重依賴於string

  • 須要本身處理superclass的observe事務

  • 屢次相同KVO的removeObserve會致使crash

而後直接上肖哥的一篇博客:一句代碼,更加優雅的調用KVO和通知

note:如下內容有興趣讀源碼的同窗能夠看看,不然直接看下一小節吧。

他的代碼架構圖以下:

6941baebjw1f6e3mihpwpj20k20ezdg8.jpg

每一個被觀察的對象有一個本身的字典,key爲被觀察的keyPath,存的值爲一個真正的觀察者對象Target,即dict[keyPath] = target。一個keyPath對應一個target,每一個target有一個KVOBlockSet存放該keyPath下的全部Block。

1

2

3

4

//監聽_objA的name屬性

[_objA xw_addObserverBlockForKeyPath:@"name" block:^(id obj, id oldVal, id newVal) {

    NSLog(@"kvo,修改name爲%@", newVal);

}];

缺陷處理結果分析:

全部的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

- (void)resignObserver {

    [girlFoo removeObserver:boyBar forKeyPath:@"wechat"];

}

 

原文地址:http://www.cocoachina.com/ios/20160803/17265.html

相關文章
相關標籤/搜索