KVO與KVC的實現(xiàn)原理

一、KVO的實現(xiàn)原理

KVO的全稱是Key-ValueObserving(鍵值監(jiān)聽),可以用于監(jiān)聽某個對象屬性值的改變。

1、KVO的使用API如下


通過addObserver: forKeyPath: options: context:添加觀察者對某個屬性的監(jiān)聽。

2、大家看下我寫的代碼,在設(shè)置age打個斷點來調(diào)試。


為什么會這樣?明明我的person實例是HPPerson類實例化而來的切發(fā)現(xiàn)他的類對象不再是HPPerson了,變成了NSKVONotifying_HPPerson。person1的類對象還是HPPerson。

注意:不能用[self.person class]來獲取解析本質(zhì)。[self.person class],[self.person1 class]得到的結(jié)果都是HPPerson類,這個可能是蘋果API不想你知道太多內(nèi)層的實現(xiàn)哈哈。所以要利用isa指針的本質(zhì)來看問題。

說明:在使用[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];時,在內(nèi)部實現(xiàn)中通過Runtime動態(tài)生成了一個NSKVONotifying_HPPerson類。

3、NSKVONotifying_HPPerson類是什么類?

先看下setAge的實現(xiàn)。


可以看出,由于person加了KVO監(jiān)聽,所以setAge的函數(shù)地址變了,實現(xiàn)也變了!setAge里面的具體實現(xiàn)是調(diào)用了_NSSetIntValueAndNotify()這個函數(shù)。但是我們從self.person.age=10;的設(shè)置結(jié)果可以看出,self.person.age設(shè)值后的age確實是10;也就是說這個成員變量_age是正確按照setAge:里面的實現(xiàn)(_age=age)做了賦值操作。而這個賦值操作時在Person類對象的方法列表中的setAge方法里面。說明_NSSetIntValueAndNotify()函數(shù)應(yīng)該實現(xiàn)了setAge的方法實現(xiàn)。也就是要達(dá)到這種目的,只有一種情況,NSKVONotifying_HPPerson是HPPerson的子類。也就是在addObserver:時候Runtime動態(tài)創(chuàng)建的NSKVONotifying_HPPerson類是HPPerson類的子類。

可以通過LLDB打印他的superclass指針來證明NSKVONotifying_HPPerson確實是HPPerson子類。


由此可以看出添加KVO后的實例對象的類對象變成了NSKVONotifying_HPPerson類,而沒有添加KVO的實例對象的類對象還是之前的HPPerson類。

總結(jié)關(guān)系如下圖:

1)添加KVO的類關(guān)系圖譜


2)沒有添加KVO的類關(guān)系圖譜


3)如果增加一個stirng類型的name屬性,則會調(diào)用_NSSetCharValueAndNotify(),由此可以知道_NSSet**ValueAndNotify()有非常多個這個函數(shù),根據(jù)不同類型屬性調(diào)用不同函數(shù)名的函數(shù),這個屬于Foundation框架下的函數(shù),可以通過越獄設(shè)備拿到Foundation的二進(jìn)制文件,然后反編譯后去查找這類函數(shù)的實現(xiàn)。過程太復(fù)雜,這里不深入寫下去了。 在這里只要知道我們的KVO實質(zhì)是利用了Runtime生成了一個子類,在子類的setAge方法中調(diào)用_NSSetIntValueAndNotify()來實現(xiàn)的。

4)_NSSetIntValueAndNotify()這系列函數(shù)內(nèi)部實現(xiàn)邏輯大概是如下這樣:

通過willChangeValueForKey與didChangeValueForKey的調(diào)用,可以實現(xiàn)手動觸發(fā)KVO監(jiān)聽。

所以可以大概知道這個函數(shù)內(nèi)部實現(xiàn)應(yīng)該是如下:


在didChangeValueForKey:里面實現(xiàn)了內(nèi)部調(diào)用屬性觀察者observer的observeValueForKeyPath:ofObject:change:context:方法。

4、通過KVC設(shè)值age會不會觸發(fā)KVO監(jiān)聽嗎?


結(jié)果可以發(fā)現(xiàn),KVC也會觸發(fā)KVO監(jiān)聽。其內(nèi)部也是實現(xiàn)了willChangeValueForKey與didChangeValueForKey的調(diào)用。

二、KVC設(shè)值取值原理流程圖

常用API使用:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;

- (void)setValue:(id)value forKey:(NSString *)key;

- (id)valueForKeyPath:(NSString *)keyPath;

- (id)valueForKey:(NSString *)key;

1、設(shè)值原理

說明:+(BOOL)accessInstanceVariablesDirectly方法默認(rèn)返回值YES;

2)取值原理


對于KVC的設(shè)值取值的順序,可以自己建一個類把對應(yīng)方法寫進(jìn)去看下執(zhí)行及順序。

三、總結(jié)

0、利用addObserver:forKeyPath...函數(shù)增加觀察值變化,內(nèi)部實現(xiàn)會利用Runtime動態(tài)生成一個NSKVONotifying_HPPerson類(HPPerson的子類)。NSKVONotifying_HPPerson類中也有setAge方法,實現(xiàn)代碼是直接調(diào)用了Foundation下的_NSSet***ValueAndNotify()系里函數(shù)。

1、_NSSet***ValueAndNotify()系里函數(shù)實現(xiàn)邏輯為調(diào)用willChangeValueForKey與didChangeValueForKey方法來實現(xiàn)。

2、-(void)didChangeValueForKey方法內(nèi)部實現(xiàn)[observer?observeValueForKeyPath: ofObject...]的調(diào)用。

3、KVO需要通過set方法來實現(xiàn)鍵值監(jiān)聽,如果直接賦值成員變量是不會觸發(fā)KVO。

4、KVO可以通過手動調(diào)用-(void)willChangeValueForKey與-(void)didChangeValueForKey方法來實現(xiàn)。

5、KVC設(shè)值會觸發(fā)KVO。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容