@[TOC](IOS底層(三) KVO底層實(shí)現(xiàn)原理 )
一草巡,KVO簡(jiǎn)述
KVO的全稱(chēng) Key-Value Observing,俗稱(chēng)“鍵值監(jiān)聽(tīng)”山憨,可以用于監(jiān)聽(tīng)某個(gè)對(duì)象屬性值的改變郁竟。
帶著問(wèn)題探索:
- iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO棚亩?(KVO的本質(zhì)是什么虏杰?)
答. 當(dāng)一個(gè)對(duì)象使用了KVO監(jiān)聽(tīng)纺阔,iOS系統(tǒng)會(huì)修改這個(gè)對(duì)象的isa指針笛钝,
改為指向一個(gè)全新的通過(guò)Runtime動(dòng)態(tài)創(chuàng)建的子類(lèi)愕宋,子類(lèi)擁有自己的set方法實(shí)現(xiàn)玻靡,
set方法實(shí)現(xiàn)內(nèi)部會(huì)順序調(diào)用willChangeValueForKey方法、原來(lái)的setter方法實(shí)現(xiàn)掏婶、
didChangeValueForKey方法啃奴,而didChangeValueForKey方法內(nèi)部
又會(huì)調(diào)用監(jiān)聽(tīng)器的observeValueForKeyPath:ofObject:change:context:監(jiān)聽(tīng)方法。
- 如何手動(dòng)觸發(fā)KVO
答. 被監(jiān)聽(tīng)的屬性的值被修改時(shí)雄妥,就會(huì)自動(dòng)觸發(fā)KVO最蕾。
如果想要手動(dòng)觸發(fā)KVO,則需要我們自己調(diào)用willChangeValueForKey和
didChangeValueForKey方法即可在不改變屬性值的情況下手動(dòng)觸發(fā)KVO
老厌,并且這兩個(gè)方法缺一不可瘟则。
- KVO 底層實(shí)現(xiàn)是什么?
- 修改成員變量的值會(huì)出發(fā) KVO 嗎?
- KVC 賦值會(huì)出發(fā) KVO 嗎?
二,KVC 簡(jiǎn)述
1. KVC定義
KVC: Key-value coding is a mechanism for indirectly accessing anobject’s attributes and relationships using string identifiers.
所謂鍵值編碼枝秤,并不是訪(fǎng)問(wèn)器方法的啟動(dòng)和實(shí)例變量的訪(fǎng)問(wèn)這種直接的方式醋拧,而是使用表示屬性的字符串來(lái)間接訪(fǎng)問(wèn)對(duì)象屬性值的一種結(jié)構(gòu)。
只要存在訪(fǎng)問(wèn)器方法淀弹、聲明屬性或?qū)嵗兞康ず荆涂梢詫⑵涿种付樽址畞?lái)訪(fǎng)問(wèn)。
之所以說(shuō)鍵值編碼的訪(fǎng)問(wèn)是接的:
可以在運(yùn)行中確定作為鍵的字符串
使用者無(wú)法知道實(shí)際訪(fǎng)問(wèn)屬性的方法
鍵值編碼必需的方法在非正式協(xié)議NSKeyValueCoding中聲明(頭文件Foundation/NSKeyValueCoding.h)薇溃。默認(rèn)在NSObject中實(shí)現(xiàn)。
2. 方法調(diào)用
下面就以下兩個(gè)方法的調(diào)用進(jìn)行說(shuō)明:
(id) valueForKey: (NSString *) key
返回表示屬性的鍵字符串所對(duì)應(yīng)的值。如果不能取得值邑时,則將引起接收器調(diào)用方法valueForUndefinedKey:。
(void)setValue: (id) value forKey: (NSString*) key
將鍵字符串key所對(duì)應(yīng)的屬性的值設(shè)置為value滤钱。不能設(shè)定屬性時(shí)铜靶,將引起接收器調(diào)用方法setValue:ForUndefinedKey:。
執(zhí)行時(shí)哩掺,有訪(fǎng)問(wèn)器的屬性會(huì)使用訪(fǎng)問(wèn)器,沒(méi)有訪(fǎng)問(wèn)器的屬性也可以設(shè)定值和訪(fǎng)問(wèn)。因?yàn)樯厦鎯蓚€(gè)方法均為實(shí)例方法誊稚,可以在方法體內(nèi)訪(fǎng)問(wèn)實(shí)例變量。
訪(fǎng)問(wèn)過(guò)程如下:
接收器中如果有key訪(fǎng)問(wèn)器(或getKey、isKey爷贫、_key、_getKey腾务、setKey)則使用它。
沒(méi)有訪(fǎng)問(wèn)器時(shí)叙凡,使用接收器的類(lèi)方法accessInstanceVariablesDirectly來(lái)查詢(xún)。返回YES時(shí),如果存在實(shí)例變量key(或_key燥撞、isKey、_isKey等)則返回或設(shè)置其值茶鉴。使用引用計(jì)數(shù)管理方式時(shí)伞插,實(shí)例變量如果為對(duì)象舀瓢,則舊值會(huì)被自動(dòng)釋放,新值被保存并代入。
-
(BOOL)accessInstanceVariablesDirectly
通常定義為返回YES备图,可以在子類(lèi)中改變。該類(lèi)方法返回YES時(shí)盾似,使用鍵值編碼可以訪(fǎng)問(wèn)該類(lèi)的實(shí)例變量。返回NO時(shí)不可以訪(fǎng)問(wèn)。只要該方法返回YES,實(shí)例變量的可視屬性即使有@private修飾乾吻,也可以訪(fǎng)問(wèn)。
- 既沒(méi)有訪(fǎng)問(wèn)器也沒(méi)有實(shí)例變量時(shí)奢方,將引起接收器調(diào)用方法valueForUndefinedKey:或setValue:forUndefinedKey:。
(id) valueForUndefinedKey: (NSStirng *) key
不能取得鍵字符串對(duì)應(yīng)的值時(shí),從方法valueForKey:中調(diào)用該方法忠聚。默認(rèn)情況下,該方法的執(zhí)行會(huì)觸發(fā)NSUndefinedKeyException。不過(guò)欢瞪,通過(guò)在子類(lèi)中修改定義啸盏,就可以返回其他對(duì)象。
(void) setValue: (id) value forUndefinedKey: (NSString *) key
不能設(shè)置鍵字符串key對(duì)應(yīng)的屬性值時(shí),從方法setValue:forKey中調(diào)用該方法。默認(rèn)情況下吧凉,該方法的執(zhí)行會(huì)觸發(fā)異常NSUndefinedKeyException。不過(guò),通過(guò)在子類(lèi)中修改定義傍妒,可以返回其他對(duì)象。
- 如果該返回值不是對(duì)象嗦玖,則返回被適當(dāng)?shù)膶?duì)象包裝的值;設(shè)置值時(shí)也應(yīng)先包裝成相應(yīng)的對(duì)象器瘪。
屬性為對(duì)象時(shí),該對(duì)象還可能持有屬性住拭。這時(shí)候可以用“.”連接表示鍵的字符串挽牢,這種表示方式稱(chēng)為鍵路徑趴俘。只要能找到對(duì)象磨淌,點(diǎn)和鍵多長(zhǎng)都沒(méi)有關(guān)系搪锣。
(id) valueForKeyPath:(NSString *) keyPath
以點(diǎn)切分鍵路徑,并使用第一個(gè)鍵向接收器發(fā)送valueForKey:方法狗超。然后,再使用鍵路徑的下一個(gè)鍵渗稍,向得到的對(duì)象發(fā)送valueForKey:方法,如此反復(fù)操作,返回最后獲得的對(duì)象。
(void)setValue: (id) value forKeyPath:(NSString *) keyPath
與valueForKeyPath:方法一樣取出對(duì)象,這里只對(duì)路徑中的最后一個(gè)鍵調(diào)用setValue:forKey:方法,并設(shè)定屬性值為value。
3. KVC準(zhǔn)則
隨訪(fǎng)問(wèn)器方法而改變诵冒。
使用setValue:forKey:和鍵進(jìn)行改變凯肋。此時(shí)也可能不經(jīng)由訪(fǎng)問(wèn)器。
使用setValue:forKeyPath:和鍵路徑進(jìn)行改變汽馋。此時(shí)也可能不經(jīng)由訪(fǎng)問(wèn)器侮东。不僅僅是最終的監(jiān)視對(duì)象的屬性,當(dāng)路徑中的屬性發(fā)生變化時(shí)豹芯,也會(huì)被通知木缝。
三,KVO實(shí)現(xiàn)原理探索
- 首先需要了解KVO基本使用饺藤,KVO的全稱(chēng) Key-Value Observing,俗稱(chēng)“鍵值監(jiān)聽(tīng)”蔽挠,可以用于監(jiān)聽(tīng)某個(gè)對(duì)象屬性值的改變蚌铜。KVO:key-value observing,是在KVC基礎(chǔ)上實(shí)現(xiàn)的,當(dāng)某個(gè)對(duì)象的屬性發(fā)生改變時(shí)难捌,通知其它對(duì)象的機(jī)制。僅僅在以KVC準(zhǔn)則來(lái)訪(fǎng)問(wèn)訪(fǎng)問(wèn)器或?qū)嵗兞康那闆r下,才可以監(jiān)視屬性的變化辛燥。在方法內(nèi)直接改變實(shí)例變量的值時(shí)辉懒,就不能監(jiān)視了窃判。
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
p1.age = 1;
p1.age = 2;
p2.age = 2;
// self 監(jiān)聽(tīng) p1的 age屬性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];
p1.age = 10;
[p1 removeObserver:self forKeyPath:@"age"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"監(jiān)聽(tīng)到%@的%@改變了%@", object, keyPath,change);
}
// 打印內(nèi)容
監(jiān)聽(tīng)到<Person: 0x604000205460>的age改變了{(lán)
kind = 1;
new = 10;
old = 2;
}
上述代碼中可以看出,在添加監(jiān)聽(tīng)之后,age屬性的值在發(fā)生改變時(shí)募狂,就會(huì)通知到監(jiān)聽(tīng)者,執(zhí)行監(jiān)聽(tīng)者的observeValueForKeyPath方法。
1. 探尋KVO底層實(shí)現(xiàn)原理
通過(guò)上述代碼我們發(fā)現(xiàn)夺艰,一旦age屬性的值發(fā)生改變時(shí),就會(huì)通知到監(jiān)聽(tīng)者,并且我們知道賦值操作都是調(diào)用 set方法遍略,我們可以來(lái)到Person類(lèi)中重寫(xiě)age的set方法盹愚,觀(guān)察是否是KVO在set方法內(nèi)部做了一些操作來(lái)通知監(jiān)聽(tīng)者。
我們發(fā)現(xiàn)即使重寫(xiě)了set方法魔种,p1對(duì)象和p2對(duì)象調(diào)用同樣的set方法析二,但是我們發(fā)現(xiàn)p1除了調(diào)用set方法之外還會(huì)另外執(zhí)行監(jiān)聽(tīng)器的observeValueForKeyPath方法。
說(shuō)明KVO在運(yùn)行時(shí)獲取對(duì)p1對(duì)象做了一些改變节预。相當(dāng)于在程序運(yùn)行過(guò)程中叶摄,對(duì)p1對(duì)象做了一些變化,使得p1對(duì)象在調(diào)用setage方法的時(shí)候可能做了一些額外的操作安拟,所以問(wèn)題出在對(duì)象身上蛤吓,兩個(gè)對(duì)象在內(nèi)存中肯定不一樣,兩個(gè)對(duì)象可能本質(zhì)上并不一樣糠赦。接下來(lái)來(lái)探索KVO內(nèi)部是怎么實(shí)現(xiàn)的会傲。
2. KVO底層實(shí)現(xiàn)分析
- 首先我們對(duì)上述代碼中添加監(jiān)聽(tīng)的地方打斷點(diǎn),看觀(guān)察一下愉棱,addObserver方法對(duì)p1對(duì)象做了什么處理唆铐?也就是說(shuō)p1對(duì)象在經(jīng)過(guò)addObserver方法之后發(fā)生了什么改變,我們通過(guò)打印isa指針如下圖所示
通過(guò)上圖我們發(fā)現(xiàn)奔滑,p1對(duì)象執(zhí)行過(guò)addObserver操作之后艾岂,p1對(duì)象的isa指針由之前的指向類(lèi)對(duì)象Person變?yōu)橹赶騈SKVONotifyin_Person類(lèi)對(duì)象,而p2對(duì)象沒(méi)有任何改變朋其。也就是說(shuō)一旦p1對(duì)象添加了KVO監(jiān)聽(tīng)以后王浴,其isa指針就會(huì)發(fā)生變化,因此set方法的執(zhí)行效果就不一樣了梅猿。
那么我們先來(lái)觀(guān)察p2對(duì)象在內(nèi)容中是如何存儲(chǔ)的氓辣,然后對(duì)比p2來(lái)觀(guān)察p1。
首先我們知道袱蚓,p2在調(diào)用setage方法的時(shí)候钞啸,首先會(huì)通過(guò)p2對(duì)象中的isa指針找到Person類(lèi)對(duì)象,然后在類(lèi)對(duì)象中找到setage方法。然后找到方法對(duì)應(yīng)的實(shí)現(xiàn)体斩。如下圖所示
但是剛才我們發(fā)現(xiàn)p1對(duì)象的isa指針在經(jīng)過(guò)KVO監(jiān)聽(tīng)之后已經(jīng)指向了NSKVONotifyin_Person類(lèi)對(duì)象梭稚,NSKVONotifyin_Person其實(shí)是Person的子類(lèi),那么也就是說(shuō)其superclass指針是指向Person類(lèi)對(duì)象的絮吵,NSKVONotifyin_Person是runtime在運(yùn)行時(shí)生成的弧烤。那么p1對(duì)象在調(diào)用setage方法的時(shí)候,肯定會(huì)根據(jù)p1的isa找到NSKVONotifyin_Person蹬敲,在NSKVONotifyin_Person中找setage的方法及實(shí)現(xiàn)暇昂。
經(jīng)過(guò)查閱資料我們可以了解到。
NSKVONotifyin_Person中的setage方法中其實(shí)調(diào)用了 Fundation框架中C語(yǔ)言函數(shù) _NSsetIntValueAndNotify伴嗡,_NSsetIntValueAndNotify內(nèi)部做的操作相當(dāng)于急波,首先調(diào)用willChangeValueForKey 將要改變方法,之后調(diào)用父類(lèi)的setage方法對(duì)成員變量賦值闹究,最后調(diào)用didChangeValueForKey已經(jīng)改變方法幔崖。didChangeValueForKey中會(huì)調(diào)用監(jiān)聽(tīng)器的監(jiān)聽(tīng)方法,最終來(lái)到監(jiān)聽(tīng)者的observeValueForKeyPath方法中渣淤。
- 那么如何驗(yàn)證KVO真的如上面所講的方式實(shí)現(xiàn)赏寇?
首先經(jīng)過(guò)之前打斷點(diǎn)打印isa指針,我們已經(jīng)驗(yàn)證了价认,在執(zhí)行添加監(jiān)聽(tīng)的方法時(shí)嗅定,會(huì)將isa指針指向一個(gè)通過(guò)runtime創(chuàng)建的Person的子類(lèi)NSKVONotifyin_Person。
另外我們可以通過(guò)打印方法實(shí)現(xiàn)的地址來(lái)看一下p1和p2的setage的方法實(shí)現(xiàn)的地址在添加KVO前后有什么變化用踩。
// 通過(guò)methodForSelector找到方法實(shí)現(xiàn)的地址
NSLog(@"添加KVO監(jiān)聽(tīng)之前 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"添加KVO監(jiān)聽(tīng)之后 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
我們發(fā)現(xiàn)在添加KVO監(jiān)聽(tīng)之前渠退,p1和p2的setAge方法實(shí)現(xiàn)的地址相同,而經(jīng)過(guò)KVO監(jiān)聽(tīng)之后脐彩,p1的setAge方法實(shí)現(xiàn)的地址發(fā)生了變化碎乃,我們通過(guò)打印方法實(shí)現(xiàn)來(lái)看一下前后的變化發(fā)現(xiàn),確實(shí)如我們上面所講的一樣惠奸,p1的setAge方法的實(shí)現(xiàn)由Person類(lèi)方法中的setAge方法轉(zhuǎn)換為了C語(yǔ)言的Foundation框架的_NSsetIntValueAndNotify函數(shù)梅誓。
Foundation框架中會(huì)根據(jù)屬性的類(lèi)型,調(diào)用不同的方法佛南。例如我們之前定義的int類(lèi)型的age屬性梗掰,那么我們看到Foundation框架中調(diào)用的_NSsetIntValueAndNotify函數(shù)。那么我們把a(bǔ)ge的屬性類(lèi)型變?yōu)閐ouble重新打印一遍
我們發(fā)現(xiàn)調(diào)用的函數(shù)變?yōu)榱薩NSSetDoubleValueAndNotify嗅回,那么這說(shuō)明Foundation框架中有許多此類(lèi)型的函數(shù)及穗,通過(guò)屬性的不同類(lèi)型調(diào)用不同的函數(shù)。
那么我們可以推測(cè)Foundation框架中還有很多例如_NSSetBoolValueAndNotify绵载、_NSSetCharValueAndNotify埂陆、_NSSetFloatValueAndNotify苛白、_NSSetLongValueAndNotify等等函數(shù)。
我們可以找到Foundation框架文件焚虱,通過(guò)命令行查詢(xún)關(guān)鍵字找到相關(guān)函數(shù)
- NSKVONotifyin_Person內(nèi)部結(jié)構(gòu)是怎樣的丸氛?
首先我們知道诬滩,NSKVONotifyin_Person作為Person的子類(lèi)饲齐,其superclass指針指向Person類(lèi)衅码,并且NSKVONotifyin_Person內(nèi)部一定對(duì)setAge方法做了單獨(dú)的實(shí)現(xiàn),那么NSKVONotifyin_Person同Person類(lèi)的差別可能就在于其內(nèi)存儲(chǔ)的對(duì)象方法及實(shí)現(xiàn)不同谍咆。
我們通過(guò)runtime分別打印Person類(lèi)對(duì)象和NSKVONotifyin_Person類(lèi)對(duì)象內(nèi)存儲(chǔ)的對(duì)象方法
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
p1.age = 1.0;
Person *p2 = [[Person alloc] init];
p1.age = 2.0;
// self 監(jiān)聽(tīng) p1的 age屬性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];
[self printMethods: object_getClass(p2)];
[self printMethods: object_getClass(p1)];
[p1 removeObserver:self forKeyPath:@"age"];
}
- (void) printMethods:(Class)cls
{
unsigned int count ;
Method *methods = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
[methodNames appendFormat:@"%@ - ", cls];
for (int i = 0 ; i < count; i++) {
Method method = methods[I];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString: methodName];
[methodNames appendString:@" "];
}
NSLog(@"%@",methodNames);
free(methods);
}
上述打印內(nèi)容如下:
通過(guò)上述代碼我們發(fā)現(xiàn)NSKVONotifyin_Person中有4個(gè)對(duì)象方法。分別為setAge: class dealloc _isKVOA私股,那么至此我們可以畫(huà)出NSKVONotifyin_Person的內(nèi)存結(jié)構(gòu)以及方法調(diào)用順序摹察。
這里NSKVONotifyin_Person重寫(xiě)class方法是為了隱藏NSKVONotifyin_Person。不被外界所看到倡鲸。我們?cè)趐1添加過(guò)KVO監(jiān)聽(tīng)之后供嚎,分別打印p1和p2對(duì)象的class可以發(fā)現(xiàn)他們都返回Person。
NSLog(@"%@,%@",[p1 class],[p2 class]);
// 打印結(jié)果 Person,Person
如果NSKVONotifyin_Person不重寫(xiě)class方法峭状,那么當(dāng)對(duì)象要調(diào)用class對(duì)象方法的時(shí)候就會(huì)一直向上找來(lái)到nsobject克滴,而nsobect的class的實(shí)現(xiàn)大致為返回自己isa指向的類(lèi),返回p1的isa指向的類(lèi)那么打印出來(lái)的類(lèi)就是NSKVONotifyin_Person优床,但是apple不希望將NSKVONotifyin_Person類(lèi)暴露出來(lái)劝赔,并且不希望我們知道NSKVONotifyin_Person內(nèi)部實(shí)現(xiàn),所以在內(nèi)部重寫(xiě)了class類(lèi)胆敞,直接返回Person類(lèi)着帽,所以外界在調(diào)用p1的class對(duì)象方法時(shí),是Person類(lèi)移层。這樣p1給外界的感覺(jué)p1還是Person類(lèi)仍翰,并不知道NSKVONotifyin_Person子類(lèi)的存在。
那么我們可以猜測(cè)NSKVONotifyin_Person內(nèi)重寫(xiě)的class內(nèi)部實(shí)現(xiàn)大致為:
- (Class) class {
// 得到類(lèi)對(duì)象观话,在找到類(lèi)對(duì)象父類(lèi)
return class_getSuperclass(object_getClass(self));
}
- 驗(yàn)證didChangeValueForKey:內(nèi)部會(huì)調(diào)用observer的observeValueForKeyPath:ofObject:change:context:方法.
我們?cè)赑erson類(lèi)中重寫(xiě)willChangeValueForKey:和didChangeValueForKey:方法予借,模擬他們的實(shí)現(xiàn)。
- (void)setAge:(int)age
{
NSLog(@"setAge:");
_age = age;
}
- (void)willChangeValueForKey:(NSString *)key
{
NSLog(@"willChangeValueForKey: - begin");
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey: - end");
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(@"didChangeValueForKey: - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey: - end");
}
再次運(yùn)行來(lái)查看didChangeValueForKey的方法內(nèi)運(yùn)行過(guò)程匪燕,通過(guò)打印內(nèi)容可以看到蕾羊,確實(shí)在didChangeValueForKey方法內(nèi)部已經(jīng)調(diào)用了observer的observeValueForKeyPath:ofObject:change:context:方法。
四帽驯,KVO底層原理
- 首先創(chuàng)建一個(gè) Person 類(lèi) 內(nèi)部有個(gè) name 屬性,然后 創(chuàng)建p1 和 p2兩個(gè)實(shí)例對(duì)象,其中p1添加了kvo監(jiān)聽(tīng),p2沒(méi)有添加 kvo 監(jiān)聽(tīng),然后重寫(xiě)了 observeValueForKeyPath 方法 監(jiān)聽(tīng)Person.name 屬性發(fā)生改變時(shí)候的通知.
從本質(zhì)上來(lái)看 Person 給name賦值的時(shí)候 調(diào)用的是 setName 方法 ,無(wú)論 p1還是p2 調(diào)用的 setter 方法都是一樣的,為什么 p1改變 name 屬性值就能有通知, p2確沒(méi)有,調(diào)用的 都是同一個(gè) setName:(NSString *)name 方法,區(qū)別怎么那么大?
- 接下來(lái)打印下p1和p2的內(nèi)存地址 看看p1和p2內(nèi)存地址能不能一探究竟.
-
從 p1和 p2內(nèi)存地址上也看不出來(lái)什么東東.接著打印 p1和 p2 的 class 信息
在這里插入圖片描述 -
打印 object_getClass 試試看龟再,我們都知道object_getClass(id) 才會(huì)返回這個(gè)實(shí)例對(duì)象的真實(shí) class 類(lèi)型
在這里插入圖片描述 -
打印 setName 方法實(shí)現(xiàn)IMP指針有沒(méi)有發(fā)生改變,我們知道同一個(gè)方法的實(shí)現(xiàn) IMP 地址是不變的.
在這里插入圖片描述 這里連 setName方法都不一樣了 , 為了一探究竟 對(duì)上邊的 NSKVONotifying_Person 和 添加 KVO 之后的 imp 指針進(jìn)行進(jìn)一步研究.
-
首先 在 lldb 上輸入 imp1和 imp2
在這里插入圖片描述
發(fā)生了 imp1 方法實(shí)現(xiàn)在 Foundation 框架里的 _NSSetObjectValueAndNotify 函數(shù)中 ,而 imp2 則調(diào)用了 Person setName 方法
在這里插入圖片描述
也就是說(shuō)添加了 KVO 之后 p1 修改 name 值之后 不再調(diào)用 Person 的 setName方法 ,而 p2沒(méi)有添加 kvo 監(jiān)聽(tīng) 依然正常調(diào)用 setName:方法 ,由此可以得出 p1 添加完 KVO 監(jiān)聽(tīng)后 系統(tǒng)修改了默認(rèn)方法實(shí)現(xiàn),那么既然沒(méi)有調(diào)用 setName: 方法 為什么 p1.name 的值也發(fā)生了改變?
-
接下來(lái)我們準(zhǔn)備對(duì)剛才 NSKVONotifying_Person 類(lèi)進(jìn)行下一步研究, NSKVONotifying_Person 和 Person 有沒(méi)有內(nèi)在的聯(lián)系呢? 研究一下NSKVONotifying_Person和 Person 之間的聯(lián)系時(shí)什么?
在這里插入圖片描述
通過(guò)打印 NSKVONotifying_Person 的 superclass 和 Person 的 superclass 可以得出, NSKVONotifying_Person是一個(gè) Person 子類(lèi),那么為什么蘋(píng)果會(huì)動(dòng)態(tài)創(chuàng)建這么一個(gè) 子類(lèi)呢? NSKVONotifying_Person 這個(gè)子類(lèi) 跟 Person 內(nèi)部有哪些不同呢 ?
這個(gè)時(shí)候 我們?nèi)ポ敵鱿?Person 和 NSKVONotifying_Person 內(nèi)部的方法列表 和 屬性列表 ,看看NSKVONotifying_Person 子類(lèi)都添加了那些方法和屬性.
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
id cls1 = object_getClass(p1);
id cls2 = object_getClass(p2);
NSLog(@"添加 KVO 之前: cls1 = %@ cls2 = %@ ",cls1,cls2);
[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
cls1 = object_getClass(p1);
cls2 = object_getClass(p2);
NSString *methodList1 = [self printPersonMethods:cls1];
NSString *methodList2 = [self printPersonMethods:cls2];
NSLog(@"%@",methodList1);
NSLog(@"%@",methodList2);
// NSLog(@"添加 KVO 之后: cls1 = %@ cls2 = %@ ",cls1,cls2);
// id super_cls1 = class_getSuperclass(cls1);
// id super_cls2 = class_getSuperclass(cls2);
//
// NSLog(@"super_cls1 = %@ ,super_cls2 = %@",super_cls1,super_cls2);
//
// p1.name = @"dzb";
// p2.name = @"123";
}
- (NSString *) printPersonMethods:(id)obj {
unsigned int count = 0;
Method *methods = class_copyMethodList([obj class],&count);
NSMutableString *methodList = [NSMutableString string];
[methodList appendString:@"[\n"];
for (int i = 0; i<count; i++) {
Method method = methods[I];
SEL sel = method_getName(method);
[methodList appendFormat:@"%@",NSStringFromSelector(sel)];
[methodList appendString:@"\n"];
}
[methodList appendFormat:@"]"];
free(methods);
return methodList;
}
輸出結(jié)果如下:
從輸出結(jié)果可以看出來(lái) NSKVONotifying_Person 內(nèi)部也有一個(gè) setName:方法 還重寫(xiě)了 class 和 dealloc 方法 , _isKVOA, 那么我們可以大致的得出, p1添加 kVO 后 runtime 動(dòng)態(tài)的生成了一個(gè) NSKVONotifying_Person子類(lèi) 并重寫(xiě)了 setName 方法 ,那么 setName 內(nèi)部一定是做了一些事情,才會(huì)觸發(fā) observeValueForKeyPath 監(jiān)聽(tīng)方法.
- 繼續(xù)探究 NSKVONotifying_Person 子類(lèi) 重寫(xiě) setName 都做了什么?
其實(shí) setName 方法內(nèi)部 是調(diào)用了 Foundation 的 _NSSetObjectValueAndNotify 函數(shù) ,在 _NSSetObjectValueAndNotify 內(nèi)部:
- 首先會(huì)調(diào)用 willChangeValueForKey
- 然后給 name 屬性賦值
- 最后調(diào)用 didChangeValueForKey
- 最后調(diào)用 observer 的 observeValueForKeyPath 去告訴監(jiān)聽(tīng)器屬性值發(fā)生了改變 .
-
由于蘋(píng)果 Foundation 框架是不開(kāi)源的 ,所以我們依然可以通過(guò)重寫(xiě)Person 的 willChangeValueForKey 和 didChangeValueForKey 驗(yàn)證我們的猜想 .
在這里插入圖片描述
首先當(dāng)我們改變p1.name 的值時(shí) 并不是首先執(zhí)行的 setName: 這個(gè)方法 ,而是先調(diào)用了 willChangeValueForKey 其次 調(diào)用父類(lèi)的 setter 方法 對(duì)屬性賦值 ,然后再調(diào)用 didChangeValueForKey 方法 ,并在 didChangeValueForKey 內(nèi)部 調(diào)用監(jiān)聽(tīng)器的 observeValueForKeyPath方法 告訴外界 屬性值發(fā)生了改變.
至于重寫(xiě)了 dealloc 和 class 方法 是為了做一些 KVO 釋放內(nèi)存 和 隱藏外界對(duì)于 NSKVONotifying_Person 子類(lèi)的存在
10.這就是我們調(diào)用 [p1 class] 和 [p2 class]結(jié)果都顯示 Person 類(lèi) ,讓我們誤以為 Person 沒(méi)有發(fā)生變化
- KVC 對(duì)屬性賦值時(shí)候 是會(huì)在這個(gè)類(lèi)里邊 去查找 _age isAge setAge setIsAge 等方法的 ,最終會(huì)調(diào)用屬性的 setter 方法 ,那么如果添加了 KVO 還是會(huì)被觸發(fā)的 .
相反 設(shè)置成員變量 _age 由于不會(huì)觸發(fā) setter 方法 ,因此不會(huì)去觸發(fā) KVO 相關(guān)的代碼 .
五尼变,KVO底層實(shí)現(xiàn)代碼
1. 通過(guò)代碼來(lái)自己實(shí)現(xiàn)KVO監(jiān)聽(tīng)
- ViewController調(diào)用實(shí)現(xiàn)
#import "ViewController.h"
#import "Person.h"
#import "NSObject+KCKVO.h"
#import "Dog.h"
@interface ViewController ()
@property (nonatomic, strong) Person *p;
@end
// 分類(lèi)
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.p = [[Person alloc] init];
[self.p lg_addObserver:self forKeyPath:@"name"];
self.p.name = @"kongyulu";
}
#pragma mark - value 回調(diào)
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object newValue:(id)newValue{
NSLog(@"lg_observeValueForKeyPath - %@",newValue);
}
#pragma mark - dealloc
- (void)dealloc{
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.p.name = [NSString stringWithFormat:@"%@+",self.p.name];
}
@end
- 定義兩個(gè)類(lèi)
- 定義Person類(lèi)
#import <Foundation/Foundation.h>
@interface Person : NSObject{
@public
NSString *girl;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, strong) NSMutableArray *mArray;
+ (instancetype)shared;
@end
#import "Person.h"
@implementation Person
- (void)setName:(NSString *)name{
NSLog(@"設(shè)置方法 ");
}
- (void)dealloc{
NSLog(@"父走了");
}
@end
- 定義Dog類(lèi)
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Dog : Person
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
#import "Dog.h"
@implementation Dog
- (void)dealloc{
NSLog(@"兒子走了");
}
@end
- 定義NSObject的一個(gè)實(shí)現(xiàn)KVO監(jiān)聽(tīng)的分類(lèi)NSObject+KCKVO
- 頭文件
#import <Foundation/Foundation.h>
typedef void(^KCKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
@interface NSObject (KCKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
- 實(shí)現(xiàn)類(lèi)
#import "NSObject+KCKVO.h"
#import <objc/message.h>
static NSString *const kKCKVOPrefix = @"KCKVO_";
static NSString *const kKCKVOAssiociateKey = @"kKCKVO_AssiociateKey";
@implementation NSObject (KCKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
// 1: 是否有setter方法
// id superClassName = object_getClassName(self); // person
// setName
NSString *setterMethodName = setterForGetter(keyPath); // setName:
SEL setterSel = NSSelectorFromString(setterMethodName);
// method
Method method = class_getInstanceMethod([self class], setterSel);// runtime 1900009931
if (!method) {
@throw [[NSException alloc] initWithName:NSExtensionItemAttachmentsKey reason:@"沒(méi)有setter方法" userInfo:nil];
}
//2: 動(dòng)態(tài)生成子類(lèi)
Class childClass = [self creatChildClassWithKeypath:keyPath];
if (!childClass) {
NSLog(@"創(chuàng)建失敗");
}
// 3.0 消息轉(zhuǎn)發(fā)
// observer
// 關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kKCKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - 動(dòng)態(tài)創(chuàng)建子類(lèi)
- (Class)creatChildClassWithKeypath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);//person
NSString *childClassName = [NSString stringWithFormat:@"%@%@",kKCKVOPrefix,oldClassName];
//2: 動(dòng)態(tài)生成子類(lèi)
//2.1 申請(qǐng)類(lèi)
Class childClass = objc_allocateClassPair([self class], childClassName.UTF8String, 0);
//2.2 注冊(cè)類(lèi)
objc_registerClassPair(childClass);
//2.3 添class
SEL classSel = NSSelectorFromString(@"class");
Method classMethod = class_getClassMethod([self class], classSel);
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(childClass, classSel, (IMP)lg_Class, classType);
//2.4 setter : setName:
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getClassMethod([self class], setterSel);
const char *setterType = method_getTypeEncoding(setterMethod);
class_addMethod(childClass, setterSel, (IMP)lg_setter, setterType);
//2.5 isa 指向
object_setClass(self, childClass);
return childClass;
}
/**
判斷是否存在該方法
*/
- (BOOL)hasSeletor:(SEL)selector{
Class observedClass = object_getClass(self);
unsigned int methodCount = 0;
//得到一堆方法的名字列表 //class_copyIvarList 實(shí)例變量 //class_copyPropertyList 得到所有屬性名字
Method *methodList = class_copyMethodList(observedClass, &methodCount);
for (int i = 0; i<methodCount; i++) {
SEL sel = method_getName(methodList[I]);
if (selector == sel) {
free(methodList);
return YES;
}
}
free(methodList);
return NO;
}
#pragma mark - 函數(shù)區(qū)域
static Class lg_Class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
static void lg_setter(id self,SEL _cmd,id value){
NSLog(@"lg_setter - %@",value);
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kKCKVOAssiociateKey));
SEL handlSEL = @selector(lg_observeValueForKeyPath: ofObject:newValue:);
NSString *keypath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,handlSEL,keypath,self,value);
// [observer performSelector:@selector(lg_observeValueForKeyPath: ofObject:newValue:) withObject:self afterDelay:0];
}
#pragma mark - 從get方法獲取set方法的名稱(chēng) name ===>>> setName:
static NSString * setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil; }
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
#pragma mark - 從set方法獲取getter方法的名稱(chēng) setName:===> name
static NSString * getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
return getter;
}
@end
-
運(yùn)行打印結(jié)果:
在這里插入圖片描述
2. 通過(guò) runtime 動(dòng)態(tài)創(chuàng)建子類(lèi)方式去實(shí)現(xiàn)
- 動(dòng)態(tài)創(chuàng)建一個(gè) NSKVONotifying_Person 子類(lèi)
/**
運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建子類(lèi)
@param super_cls 父類(lèi)
@return 返回子類(lèi)
*/
- (Class) registerSubClassWithSuperClass:(Class)super_cls {
///動(dòng)態(tài)的創(chuàng)建 子類(lèi)
NSString *clsName = [NSString stringWithFormat:@"NSKVONotifying_%@",super_cls];
///一個(gè) NSObject 默認(rèn)分配16個(gè)字節(jié)內(nèi)存
Class sub_cls = objc_allocateClassPair(super_cls,clsName.UTF8String,16);
///注冊(cè)一個(gè)子類(lèi)
objc_registerClassPair(sub_cls);
///將父類(lèi) isa 指針指向 子類(lèi)
object_setClass(self, sub_cls);
return sub_cls;
}
- 動(dòng)態(tài)的給這個(gè)子類(lèi) 動(dòng)態(tài)添加方法 setter 方法 didChangeValueForKey方法 class 方法實(shí)現(xiàn)
///動(dòng)態(tài)創(chuàng)建子類(lèi) NSKVONotifying_xxx
Class sub_cls = [self registerSubClassWithSuperClass:super_cls];
///給子類(lèi)動(dòng)態(tài)的添加 class setter didChangeValueForKey 實(shí)現(xiàn)
Method class_method = class_getInstanceMethod(super_cls, @selector(class));
Method changeValue_method = class_getInstanceMethod(super_cls, @selector(didChangeValueForKey:));
class_addMethod(sub_cls, @selector(class), (IMP)kvo_class,method_getTypeEncoding(class_method));
///給子類(lèi)動(dòng)態(tài)的添加 didChangeValueForKey
class_addMethod(sub_cls, @selector(didChangeValueForKey:), (IMP)didChangeValue,method_getTypeEncoding(changeValue_method));
///動(dòng)態(tài)的給子類(lèi)添加 setter 方法
class_addMethod(sub_cls, setterSel, (IMP)kvo_setter,method_getTypeEncoding(method));
///將觀(guān)察者對(duì)象跟當(dāng)前實(shí)例 self 關(guān)聯(lián)起來(lái)
objc_setAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedObservers), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- 重寫(xiě) class 方法實(shí)現(xiàn)
/**
自實(shí)現(xiàn) class 方法
@param self 當(dāng)前類(lèi)實(shí)現(xiàn)
@param _cmd class
@return 返回父類(lèi) Class 外界不會(huì)知道 NSKVONotifying_子類(lèi)存在
*/
static Class kvo_class(id self,SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
- 重寫(xiě) setter 方法實(shí)現(xiàn)
/**
自實(shí)現(xiàn) setter 方法
@param self 當(dāng)前類(lèi)實(shí)現(xiàn)
@param _cmd setter
@param newValue 賦值
*/
static void kvo_setter(id self,SEL _cmd,id newValue) {
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterForSetter(setterName);
///將要改變屬性的值
[self willChangeValueForKey:getterName];
///調(diào)用 super setter 方法
struct objc_super suer_cls = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
///存儲(chǔ)舊值
objc_setAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue),[self valueForKey:getterName], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
///調(diào)用父類(lèi) setter 方法 設(shè)置新值
objc_msgSendSuper(&suer_cls,_cmd,newValue);
///改變監(jiān)聽(tīng)屬性值后 調(diào)用 didChangeValueForKey 并在內(nèi)部 調(diào)用
[self didChangeValueForKey:getterName];
};
- 重寫(xiě) didChangeValueForKey 方法實(shí)現(xiàn)
/**
didChangeValueForkey 實(shí)現(xiàn)方法 , 當(dāng)根據(jù) SEL (didChangeValueForkey:) 會(huì)找到方法 IMP 實(shí)現(xiàn)
*/
static void didChangeValue(id self,SEL _cmd,NSString *key) {
id newValue = [self valueForKey:key];
id observer = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedObservers));
id oldValue = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(KVOAssociatedOldValue));
NSMutableDictionary *change = [NSMutableDictionary dictionary];
if (oldValue) {
change[@"oldValue"] = oldValue;
} else {
change[@"oldValue"] = [NSNull null];
}
if (newValue) {
change[@"newValue"] = newValue;
} else {
change[@"newValue"] = newValue;
}
[observer observeValueForKeyPath:key ofObject:self change:change context:NULL];
}