關(guān)于KVO,首先我們來看兩道面試題
KVO的本質(zhì)是什么?
如何手動去觸發(fā)KVO?
直接修改成員變量會觸發(fā)KVO么?
怎么樣勺鸦? 如果你能夠很好的回答這兩道面試題我相信你對KVO的使用以及內(nèi)部原理已經(jīng)非常熟悉了丑罪,當(dāng)然也就不用再繼續(xù)往下看了。如果對KVO的內(nèi)部實現(xiàn)原理還是一知半解那么請跟我來,我將為你一一剖析
KVO的簡單使用
首先我們來看一下KVO的簡單使用步驟:
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
p1.age = 18;
self.p1 = p1;
Person *p2 = [[Person alloc] init];
p2.age = 18;
/**
1. p1:要監(jiān)聽的對象
2. 參數(shù)說明:
* @param addObserver 觀察者棒口,負責(zé)處理監(jiān)聽事件的對象
* @param forKeyPath 要監(jiān)聽的屬性
* @param options 觀察的選項
* @param context 上下文,用于傳遞數(shù)據(jù)
*/
[self.p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
p1.age = 20; // [p1 setAge:20];
p2.age = 30; // [p2 setAge:30];
}
//2.0 當(dāng)屬性值發(fā)生改變的時候會自動觸發(fā)該方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"監(jiān)聽到%@對象的%@屬性改變---%@",object,keyPath,change);
}
//3.0 當(dāng)該控件被銷毀的時候要移除該監(jiān)聽
-(void)dealloc{
[self.p1 removeObserver:self forKeyPath:@"age"];
}
@end
上述代碼運行起來之后葫笼,執(zhí)行結(jié)果:
監(jiān)聽到<Person: 0x60000000e9f0>對象的age屬性改變---{
kind = 1;
new = 20;
old = 18;
}
不知道大家注意到?jīng)]有屹逛,在上面的代碼中我們同時改變了p1
和p2
的age
的值,
p1.age = 20; // [p1 setAge:20];
p2.age = 30; // [p2 setAge:30];
但是卻只監(jiān)聽到了p1的改變础废?你可能會說那不是廢話么.....因為你只給p1 添加了 addObserver
,是啊罕模,對于編譯器來說评腺,p1和p2對象是一樣的,唯一的區(qū)別就是我們?yōu)閜1對象添加了一個監(jiān)聽淑掌。既然這樣那為什么我們添加了監(jiān)聽之后 蘋果就自動幫助我們?nèi)ケO(jiān)聽了p1
的age
屬性值的改變的呢蒿讥?至少在編譯階段這兩個對象都是一樣的,那肯定是在程序運行階段蘋果利用運行時RunTime動了手腳.....
KVO的內(nèi)部實現(xiàn)
首先我們將程序跑起來抛腕,當(dāng)程序運行到42行的時候芋绸,也就是沒有添加任何監(jiān)聽之前,我們在打印一下p1對象和p2對象的isa指針摔敛,可以發(fā)現(xiàn)他們都是指向了同一個類對象也就是Person對象,如下圖所示:
此時讓程序繼續(xù)往下走,跳過42行我們再次打印 可以發(fā)現(xiàn)此時p1對象的isa指針已經(jīng)悄悄的指向了另外一個對象 NSKVONotifying_Person
全封,而p2對象的isa指針依然指向Person
對象桃犬,如下圖所示:
也就是說正是因為p1對象的isa指針指向了另外一個對象,這才導(dǎo)致了p1對象和p2對象在調(diào)用setAge
方法時候的差異.
除了上述打斷點的方法查看攒暇,我們還可以通過Runtime
中的object_getClass
來分析下在添加KVO前后的變化,如下圖所示:
我們可以看到在添加了KVO監(jiān)聽之后子房,p1對象的isa指針依然是指向了
NSKVONotifying_Person
對象形用,也驗證了我們上面的推斷:
接下來我們來仔細分析一下這個過程:
首先我們來分析一下沒有添加KVO監(jiān)聽的p2對象: 正如上面我們斷點所看到的,因為p2對象沒有添加KVO監(jiān)聽池颈,所以p2對象內(nèi)部的isa指針依然是指向Person
對象尾序。所以當(dāng)我們調(diào)用p2.age = 30; // [p2 setAge:30];
的時候它內(nèi)部其實是先根據(jù)實例對象(instance對象)的isa指針找到它對應(yīng)的類對象(Class對象)Person,然后調(diào)用它里面的setAge
方法,并無異常,如下圖所示:
接下來我們再來分析一下添加了KVO監(jiān)聽的p1對象躯砰。從上文我們可以得知,添加了KVO之后p1對象的isa指針已經(jīng)指向了NSKVONotifying_Person
對象携丁。其實該對象是iOS在運行時通過Runtime自動創(chuàng)建的Person對象的子類琢歇,并且在該類中重寫了setAge方法 大概執(zhí)行流程如下:
NSKVONotifying_Person
類中的setAge
方法大概執(zhí)行情況如下代碼:
@implementation NSKVONotifying_MJPerson1
- (void)setAge:(int)age
{
__NSSet*ValueAndNotify();
}
void __NSSet*ValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
[observer observeValueForKeyPath:@"age" ofObject:self change:@{} context:nil];
}
@end
所以通過以上分析我們可以看出在程序編譯階段p1對象和p2對象的表面都是調(diào)用setAge方法,但是在程序運行階段他們內(nèi)部的執(zhí)行卻是不一樣的
看到這里我相信你已經(jīng)可以很好地回答上面的兩道面試題目了:
KVO的本質(zhì)是什么?
KVO其實是OC利用觀察者模式
來實現(xiàn)的一個技術(shù)梦鉴。當(dāng)一個對象使用了KVO監(jiān)聽之后李茫,iOS系統(tǒng)會在運行時階段通過Runtime動態(tài)創(chuàng)建該類的子類對象,并且會修改這個對象的isa指針指向該子類對象(isa混寫技術(shù) isa-swizzling
)肥橙。在該子類對象內(nèi)部擁有自己的set方法實現(xiàn)魄宏,同時內(nèi)部會調(diào)用willChangeValueForKey
、 父類對象的setter方法 存筏、didChangeValueForKey
方法宠互。在didChangeValueForKey
方法中又調(diào)用了observeValueForKeyPath
方法。
具體的邏輯圖如下所示:
如何手動去觸發(fā)KVO?
因為p1對象添加了KVO監(jiān)聽之后椭坚,它的內(nèi)部大概就是執(zhí)行了下面的三個方法予跌,所以我們可以大膽的猜測一下∩凭ィ肯定是willChangeValueForKey
和didChangeValueForKey
這兩個方法來觸發(fā)了KVO操作
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
為了驗證我們的判斷,我們還是通過代碼來說明問題:
通過調(diào)用上面我們說的那兩個方法券册,手動的觸發(fā)了observeValueForKeyPath
方法,這也驗證了我們的判斷垂涯,那么你可能會問只調(diào)用其中的一個方法可不可以手動觸發(fā)KVO烁焙? 答案是否,必須是這兩個方法一起調(diào)用才會觸發(fā)KVO操作耕赘,不信你可以試一試哦~
直接修改成員變量會觸發(fā)KVO么?
通過以上分析我相信你肯定知道答案了骄蝇,直接修改成員變量不會觸發(fā)Set方法 自然也就不會觸發(fā)KVO了。當(dāng)然你手動調(diào)用的除外了~~
通過KVC修改屬性會觸發(fā)KVO么?
通過KVC修改成員變量的值鞠苟,系統(tǒng)內(nèi)部幫助我們調(diào)用了willChangeValueForKey
和didChangeValueForKey
這兩個方法乞榨,從而觸發(fā)了KVO秽之,具體分析參考KVC內(nèi)部執(zhí)行過程分析
總結(jié):
什么是KVO?
是觀察者模式的一種實現(xiàn)吃既,同時是利用isa混寫技術(shù)來實現(xiàn)的考榨。也就是在運行時階段通過Runtime動態(tài)創(chuàng)建該類的子類對象,并且會修改這個對象的isa指針指向該子類對象鹦倚, 在子類對象中重寫setter方法河质。
- 使用setter方法改變值KVO才會生效
- 使用setValue:forKey:改變值KVO才會生效
- 直接修改成員變量不會觸發(fā)KVO震叙,需要手動調(diào)用才會觸發(fā)。
完結(jié).....