KVO原理

探索KVO原理

有這么一個(gè)示例:

  1. Person類,有一個(gè)實(shí)例變量nickName,屬性name,對(duì)象方法run,work:
// Person.h 
@interface Person : NSObject
{
    NSString *nickName;
}

@property (nonatomic, copy) NSString *name;

- (void)run;

- (void)work;

@end

// Person.m 實(shí)現(xiàn)方法
- (void)work {
}

- (void)run {
}
  1. Student類繼承自Person類:
// Student.h 什么也沒(méi)做
@interface Student : Person

@end

// Student.m 實(shí)現(xiàn)繼承來(lái)的run方法
- (void)run {    
}
  1. 有兩個(gè)工具方法,遍歷類的方法 和 遍歷類及子類
#pragma mark - 遍歷方法

- (void)printClassAllMethod:(Class)cls {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableArray *mArray = [NSMutableArray array];
    for (int i = 0; i < count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@ -- %p", NSStringFromSelector(sel), imp);
        [mArray addObject:NSStringFromSelector(sel)];
    }
    NSLog(@"allMethod = %@", mArray);
}

#pragma mark - 遍歷類及子類

- (void)printClasses:(Class)cls {
    // 注冊(cè)類的總數(shù)
    int count = objc_getClassList(NULL, 0);
    // 創(chuàng)建一個(gè)數(shù)組,其中包含給定的對(duì)象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 獲取所有已注冊(cè)的類
    Class *classes = (Class *)malloc(sizeof(Class) * count);
    objc_getClassList(classes, count);
    for (int i = 0; i < count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [mArray addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"classes = %@", mArray);
}
  1. 調(diào)用工具方法打印Person 和 Student的所有方法及類和子類
[self printClassAllMethod:[Person class]];
[self printClassAllMethod:[Student class]];

[self printClasses:[Person class]];
[self printClasses:[Student class]];
  1. 打印結(jié)果
Person -- ClassAllMethod
2019-11-12 22:07:27.189171+0800 KVO[29094:3896664] allMethod = (
    ".cxx_destruct",
    name,
    "setName:",
    run,
    work
)

Student -- ClassAllMethod
2019-11-12 22:07:27.189791+0800 KVO[29094:3896664] allMethod = (
    run
)

Person -- Classes
2019-11-12 22:07:27.217213+0800 KVO[29094:3896664] classes = (
    Person,
    Student
)

Student -- Classes
2019-11-12 22:07:27.222664+0800 KVO[29094:3896664] classes = (
    Student
)
  • Persor類的所有方法,可以看到除了聲明的run,work,還有系統(tǒng)自動(dòng)生成的屬性的setter和getter方法,實(shí)例變量nickName沒(méi)有生成setter和getter方法
  • Student類只有繼承來(lái)的run,且方法實(shí)現(xiàn)地址imp和父類不一樣
  • Person類除了自身還有子類Student
  • Student只有自己
    》》》和我們預(yù)期的完全一樣《《《
  1. 添加一個(gè)觀察者
self.person = [[Person alloc] init];
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:nil];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];

[self printClasses:[Person class]];

打印發(fā)現(xiàn)此時(shí)的Person類及子類,多了一個(gè)NSKVONotifying_Person類,說(shuō)明NSKVONotifying_Person是動(dòng)態(tài)生成的Person的子類

2019-11-12 22:28:32.530026+0800 KVO[29303:3917560] classes = (
    Person,
    "NSKVONotifying_Person",
    Student
)

我們斷點(diǎn)觀察者注冊(cè)前后,發(fā)現(xiàn)實(shí)例對(duì)象self.person真正指向的類從Person變?yōu)榱薔SKVONotifying_Person,真是神奇。

  1. 既然有NSKVONotifying_Person這個(gè)類,那么把這個(gè)類的所有方法打印出來(lái)瞧一瞧
[self printClassAllMethod:NSClassFromString(@"NSKVONotifying_Person")];

打印結(jié)果:

2019-11-12 22:39:27.756694+0800 KVO[29389:3928692] setName: -- 0x7fff2564cec6
2019-11-12 22:39:27.756979+0800 KVO[29389:3928692] class -- 0x7fff2564b989
2019-11-12 22:39:27.757287+0800 KVO[29389:3928692] dealloc -- 0x7fff2564b6ee
2019-11-12 22:39:27.757561+0800 KVO[29389:3928692] _isKVOA -- 0x7fff2564b6e6
2019-11-12 22:39:27.757720+0800 KVO[29389:3928692] allMethod = (
    "setName:",
    class,
    dealloc,
    "_isKVOA"
)

觀察前
setName: -- 0x1038bf7d0
觀察后
setName: -- 0x7fff2564cec6

哇,看到重點(diǎn)了,除了這是KVO類的標(biāo)記和dealloc方法,看到了setName方法,再結(jié)合觀察前后setName方法對(duì)應(yīng)的方法實(shí)現(xiàn)IMP地址的不同,可以推斷NSKVONotifying_Person重寫(xiě)了setter方法,因而在對(duì)象上對(duì)setter的調(diào)用就會(huì)調(diào)用已重寫(xiě)的setter,從而激活鍵值通知機(jī)制。

總結(jié)KVO原理:

  1. Apple使用了isa混寫(xiě)(isa-swizzling)來(lái)實(shí)現(xiàn)KVO。當(dāng)觀察對(duì)象為類A的屬性時(shí),KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)新的名為:NSKVONotifying_A的新類,該類繼承自A的本類,且KVONSKVONotifying_A重寫(xiě)觀察屬性的setter方法,setter方法會(huì)負(fù)責(zé)在調(diào)用原setter方法之前和之后,通知所有觀察對(duì)象屬性值的更改情況。

  2. NSKVONotifying_A類剖析:在這個(gè)過(guò)程,被觀察對(duì)象的isa指針從指向原來(lái)的A類,被KVO機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類NSKVONotifying_A類,來(lái)實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽(tīng)。

  3. 所以當(dāng)我們從應(yīng)用層面上看來(lái),完全沒(méi)有意識(shí)到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對(duì)KVO的底層實(shí)現(xiàn)過(guò)程,讓我們誤以為還是原來(lái)的類。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為“NSKVONotifying_A”的類,就會(huì)發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊(cè)KVO的那段代碼時(shí)程序就崩潰,因?yàn)橄到y(tǒng)在注冊(cè)監(jiān)聽(tīng)的時(shí)候動(dòng)態(tài)創(chuàng)建了名為NSKVONotifying_A的中間類,并指向這個(gè)中間類了。

  4. isa指針的作用:每個(gè)對(duì)象都有isa指針,指向該對(duì)象的類,它告訴runtime系統(tǒng)這個(gè)對(duì)象的類時(shí)什么。所以對(duì)象注冊(cè)為觀察者時(shí),isa指針指向新子類,那么這個(gè)被觀察的對(duì)象就神奇地變成新子類的對(duì)象(或?qū)嵗┝恕R蚨谠搶?duì)象上對(duì)setter的調(diào)用就會(huì)調(diào)用已重寫(xiě)的setter,從而激活鍵值通知機(jī)制。

  5. 子類setter方法剖析:KVO的鍵值觀察通知依賴于NSObject的兩個(gè)方法:willChangeValueForKey:didChangeValueForKey:,在存取數(shù)值的前后分別調(diào)用2個(gè)方法:被觀察屬性發(fā)生改變之前,willChangeValueForKey:被調(diào)用,通知系統(tǒng)該keyPath的屬性值即將變更;在改變發(fā)生之后,didChangeValueForKey:被調(diào)用,通知系統(tǒng)該keyPath的屬性值已經(jīng)變更;之后,observeValueForKey:ofObject:change:context:也會(huì)被調(diào)用。且重寫(xiě)觀察屬性的setter方法這種繼承方式的注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。

示例代碼可以在這里下載:
https://github.com/SPIREJ/KVO

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • [TOC] KVO 研究 沒(méi)有使用KVO和使用KVO的變化 測(cè)試的類Person 通過(guò) objc_copyClas...
    oceanfive閱讀 515評(píng)論 0 0
  • 該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請(qǐng)注明:劉小壯[http://www.itdecent.cn/u/2de707c93d...
    劉小壯閱讀 49,047評(píng)論 35 227
  • KVC 1.簡(jiǎn)介 KVC全稱是Key Value Coding(鍵值編碼),是可以通過(guò)對(duì)象屬性名稱(Key)直接給...
    Jt_Self閱讀 618評(píng)論 0 0
  • 前言: 本文基本不講KVC/KVO的用法,只結(jié)合網(wǎng)上的資料說(shuō)說(shuō)對(duì)這種技術(shù)的理解。 由于KVO內(nèi)容較少,而且是以KV...
    土b蘭博王閱讀 3,159評(píng)論 0 33
  • 今天算是過(guò)的最奇葩的一天了,凌晨大概4點(diǎn)多的時(shí)候,竟然從床上掉下來(lái)了。而且我睡的上下鋪的上鋪呢。睡的迷迷糊糊不知道...
    勤飛揚(yáng)閱讀 302評(píng)論 0 2

友情鏈接更多精彩內(nèi)容