KVO的內(nèi)部實現(xiàn)原理

關(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)]有屹逛,在上面的代碼中我們同時改變了p1p2age的值,

    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)聽了p1age屬性值的改變的呢蒿讥?至少在編譯階段這兩個對象都是一樣的,那肯定是在程序運行階段蘋果利用運行時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對象桃犬,如下圖所示:

image

也就是說正是因為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方法,并無異常,如下圖所示:

image

接下來我們再來分析一下添加了KVO監(jiān)聽的p1對象躯砰。從上文我們可以得知,添加了KVO之后p1對象的isa指針已經(jīng)指向了NSKVONotifying_Person對象携丁。其實該對象是iOS在運行時通過Runtime自動創(chuàng)建的Person對象的子類琢歇,并且在該類中重寫了setAge方法 大概執(zhí)行流程如下:

image

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í)行了下面的三個方法予跌,所以我們可以大膽的猜測一下∩凭ィ肯定是willChangeValueForKeydidChangeValueForKey這兩個方法來觸發(fā)了KVO操作

    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];

為了驗證我們的判斷,我們還是通過代碼來說明問題:


image

通過調(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)用了willChangeValueForKeydidChangeValueForKey這兩個方法乞榨,從而觸發(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é).....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乐尊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子划址,更是在濱河造成了極大的恐慌,老刑警劉巖夺颤,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異独旷,居然都是意外死亡,警方通過查閱死者的電腦和手機嵌洼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門抚恒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人俭驮,你說我怎么就攤上這事』炻埽” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵逸嘀,是天一觀的道長。 經(jīng)常有香客問我崭倘,道長类垫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任悉患,我火速辦了婚禮榆俺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茴晋。我一直安慰自己,他們只是感情好诺擅,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烁涌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烹玉。 梳的紋絲不亂的頭發(fā)上阐滩,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音掂榔,去河邊找鬼。 笑死装获,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的穴豫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼精肃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了司抱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤习柠,失蹤者是張志新(化名)和其女友劉穎照棋,沒想到半個月后武翎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡后频,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了卑惜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡露久,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出毫痕,到底是詐尸還是另有隱情,我是刑警寧澤消请,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站臊泰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缸逃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一需频、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昭殉,春花似錦、人聲如沸饲化。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春礁阁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姥闭。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棚品,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓铜跑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锅纺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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