一祷肯、KVO的實(shí)現(xiàn)原理
KVO的全稱是Key-ValueObserving(鍵值監(jiān)聽),可以用于監(jiān)聽某個(gè)對象屬性值的改變芙代。
1之拨、KVO的使用API如下
通過addObserver: forKeyPath: options: context:添加觀察者對某個(gè)屬性的監(jiān)聽茉继。
2、大家看下我寫的代碼蚀乔,在設(shè)置age打個(gè)斷點(diǎn)來調(diào)試烁竭。
為什么會這樣?明明我的person實(shí)例是HPPerson類實(shí)例化而來的切發(fā)現(xiàn)他的類對象不再是HPPerson了,變成了NSKVONotifying_HPPerson乙墙。person1的類對象還是HPPerson颖变。
注意:不能用[self.person class]來獲取解析本質(zhì)。[self.person class]听想,[self.person1 class]得到的結(jié)果都是HPPerson類腥刹,這個(gè)可能是蘋果API不想你知道太多內(nèi)層的實(shí)現(xiàn)哈哈。所以要利用isa指針的本質(zhì)來看問題汉买。
說明:在使用[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];時(shí)衔峰,在內(nèi)部實(shí)現(xiàn)中通過Runtime動(dòng)態(tài)生成了一個(gè)NSKVONotifying_HPPerson類。
3蛙粘、NSKVONotifying_HPPerson類是什么類垫卤?
先看下setAge的實(shí)現(xiàn)。
可以看出出牧,由于person加了KVO監(jiān)聽穴肘,所以setAge的函數(shù)地址變了,實(shí)現(xiàn)也變了舔痕!setAge里面的具體實(shí)現(xiàn)是調(diào)用了_NSSetIntValueAndNotify()這個(gè)函數(shù)评抚。但是我們從self.person.age=10;的設(shè)置結(jié)果可以看出,self.person.age設(shè)值后的age確實(shí)是10伯复;也就是說這個(gè)成員變量_age是正確按照setAge:里面的實(shí)現(xiàn)(_age=age)做了賦值操作慨代。而這個(gè)賦值操作時(shí)在Person類對象的方法列表中的setAge方法里面。說明_NSSetIntValueAndNotify()函數(shù)應(yīng)該實(shí)現(xiàn)了setAge的方法實(shí)現(xiàn)啸如。也就是要達(dá)到這種目的侍匙,只有一種情況,NSKVONotifying_HPPerson是HPPerson的子類叮雳。也就是在addObserver:時(shí)候Runtime動(dòng)態(tài)創(chuàng)建的NSKVONotifying_HPPerson類是HPPerson類的子類想暗。
可以通過LLDB打印他的superclass指針來證明NSKVONotifying_HPPerson確實(shí)是HPPerson子類。
由此可以看出添加KVO后的實(shí)例對象的類對象變成了NSKVONotifying_HPPerson類帘不,而沒有添加KVO的實(shí)例對象的類對象還是之前的HPPerson類江滨。
總結(jié)關(guān)系如下圖:
1)添加KVO的類關(guān)系圖譜
2)沒有添加KVO的類關(guān)系圖譜
3)如果增加一個(gè)stirng類型的name屬性,則會調(diào)用_NSSetCharValueAndNotify(),由此可以知道_NSSet**ValueAndNotify()有非常多個(gè)這個(gè)函數(shù)厌均,根據(jù)不同類型屬性調(diào)用不同函數(shù)名的函數(shù)唬滑,這個(gè)屬于Foundation框架下的函數(shù),可以通過越獄設(shè)備拿到Foundation的二進(jìn)制文件,然后反編譯后去查找這類函數(shù)的實(shí)現(xiàn)晶密。過程太復(fù)雜擒悬,這里不深入寫下去了。 在這里只要知道我們的KVO實(shí)質(zhì)是利用了Runtime生成了一個(gè)子類稻艰,在子類的setAge方法中調(diào)用_NSSetIntValueAndNotify()來實(shí)現(xiàn)的懂牧。
4)_NSSetIntValueAndNotify()這系列函數(shù)內(nèi)部實(shí)現(xiàn)邏輯大概是如下這樣:
通過willChangeValueForKey與didChangeValueForKey的調(diào)用,可以實(shí)現(xiàn)手動(dòng)觸發(fā)KVO監(jiān)聽尊勿。
所以可以大概知道這個(gè)函數(shù)內(nèi)部實(shí)現(xiàn)應(yīng)該是如下:
在didChangeValueForKey:里面實(shí)現(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)部也是實(shí)現(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è)值取值的順序擅羞,可以自己建一個(gè)類把對應(yīng)方法寫進(jìn)去看下執(zhí)行及順序尸变。
三、總結(jié)
0减俏、利用addObserver:forKeyPath...函數(shù)增加觀察值變化召烂,內(nèi)部實(shí)現(xiàn)會利用Runtime動(dòng)態(tài)生成一個(gè)NSKVONotifying_HPPerson類(HPPerson的子類)。NSKVONotifying_HPPerson類中也有setAge方法娃承,實(shí)現(xiàn)代碼是直接調(diào)用了Foundation下的_NSSet***ValueAndNotify()系里函數(shù)骑晶。
1、_NSSet***ValueAndNotify()系里函數(shù)實(shí)現(xiàn)邏輯為調(diào)用willChangeValueForKey與didChangeValueForKey方法來實(shí)現(xiàn)草慧。
2、-(void)didChangeValueForKey方法內(nèi)部實(shí)現(xiàn)[observer?observeValueForKeyPath: ofObject...]的調(diào)用匙头。
3漫谷、KVO需要通過set方法來實(shí)現(xiàn)鍵值監(jiān)聽,如果直接賦值成員變量是不會觸發(fā)KVO蹂析。
4舔示、KVO可以通過手動(dòng)調(diào)用-(void)willChangeValueForKey與-(void)didChangeValueForKey方法來實(shí)現(xiàn)。
5电抚、KVC設(shè)值會觸發(fā)KVO惕稻。