iOS-底層原理-KVO實(shí)現(xiàn)原理

1.iOS系統(tǒng)的KVO實(shí)現(xiàn)原理

1.蘋果官方文檔解釋的是
Automatic key-value observing is implemented using a technique called isa-swizzling.
isa-swizzling,isa指針在兩個(gè)類之間交換,下面通過代碼來(lái)具體看一下isa指針的指向問題

2.isa-swizzling
ViewController觀察Person類的nickName屬性為例

1.注冊(cè)觀察者前 isa指向
正常指向了本類Person


注冊(cè)KVO前 isa指向.jpeg

2.注冊(cè)觀察者(addObserver)后 isa指向

可以看到注冊(cè)KVO isa指向了NSKVONotifying_Person,NSKVONotifying_Person這個(gè)類是iOS系統(tǒng)在注冊(cè)KVO后生成的Person類的子類或者叫派生類繼承于Person,蘋果的命名規(guī)則NSKVONotifying_本類名

注冊(cè)KVO后isa指向.jpeg

3.移除觀察者(removeObserver)后isa指向
此時(shí)觀察Person類的兩個(gè)屬性,分別是nickName和name,在button的點(diǎn)擊事件里移除觀察者

[self.m_person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:PersonNameContext];
[self.m_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];

先移除nickName,可以看到self.m_person isa依然指向NSKVONotifying_Person

先移除nickName.jpeg

再移除name,兩個(gè)屬性都移除觀察了,self.m_person isa才指向本類Person,也就是說觀察了幾個(gè)屬性,必須全部移除后isa才會(huì)指回原來(lái)的本類


再移除name.jpeg

4.通過方法打印看看這個(gè)派生子類NSKVONotifying_Person實(shí)現(xiàn)了哪些方法

打印本類有哪些的方法如下
// 遍歷方法-ivar-property
- (void)printClassAllMethod:(Class)cls {
    NSLog(@"該class有哪些方法 == %@", cls);
    unsigned int count = 0;
    Method * methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i < count; i++) {
        Method method = methodList[I];
        SEL sel = method_getName(method);
        NSLog(@"each selector == %@", NSStringFromSelector(sel));
    }
}

NSKVONotifying_Person 這個(gè)類里面的方法如下圖
我們看到實(shí)現(xiàn)了nickName的setter方法setNickName: class dealloc _isKVOA四個(gè)方法

NSKVONotifying_Person類有哪些方法.jpeg

觀察nickName和name兩個(gè)屬性

[self.m_person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:PersonNameContext];
[self.m_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];
[self printClassAllMethod:objc_getClass("NSKVONotifying_Person")];

輸出結(jié)果

2021-12-13 13:52:18.102391+0800  該class有哪些方法 == NSKVONotifying_Person
2021-12-13 13:52:18.102797+0800  each selector == setName:
2021-12-13 13:52:18.102959+0800  each selector == setNickName:
2021-12-13 13:52:18.103220+0800  each selector == class
2021-12-13 13:52:18.103359+0800  each selector == dealloc
2021-12-13 13:52:18.103569+0800  each selector == _isKVOA

5.分析一下各個(gè)方法的各自的用途

先了解一下KVO的目的

通過對(duì)屬性setter方法的賦值,達(dá)到可以監(jiān)聽對(duì)應(yīng)屬性值的變化,同時(shí)屬性值被正確修改,也就是除了可以監(jiān)聽到屬性值的變化,其他一切看起來(lái)和Person類的某個(gè)實(shí)例對(duì)該屬性賦值完全一致,完全沒有NSKVONotifying_Person這個(gè)派生子類的影子,也就是self.m_person.nickName setter getter方法不受影響,同時(shí)執(zhí)行KVO的回調(diào)方法,達(dá)到滿足功能并且沒有入侵式的目的

注冊(cè)觀察者

[self.m_person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:PersonNameContext];

對(duì)應(yīng)屬性賦值

self.m_person.nickName = [NSString stringWithFormat:@"%@+%lu", @"nickName", (unsigned long)count];

5.1 _isKVOA分析

從方法名來(lái)看,應(yīng)該是判斷是否是KVO狀態(tài)的一個(gè)方法,暫時(shí)沒辦法驗(yàn)證到底是做什么的,不過從上面的觀察多個(gè)屬性值的變化,必須全部移除才會(huì)指向Person本類,脫離KVO狀態(tài),應(yīng)該就是狀態(tài)判斷的方法

5.2 dealloc分析

析構(gòu)函數(shù),下面來(lái)看看析構(gòu)流程

前提,controller被pop掉之后,沒有在controller的dealloc里面移除對(duì)nickName的觀察

因?yàn)榇藭r(shí)self.m_person指針沒有移除nickName的觀察,所以依然指向NSKVONotifying_Person,而且先執(zhí)行了NSKVONotifying_Person的dealloc,再執(zhí)行Person dealloc,由此可以看出,NSKVONotifying_Persondealloc里面調(diào)用了父類Person dealloc,[NSKVONotifying_Person dealloc] ---> [Person dealloc],同時(shí)析構(gòu)了子類和父類

不移除觀察者dealloc執(zhí)行情況.jpeg

5.3 class分析

addObserver之前isa和class指向

isa指向Person

class指向Person

addObserver之前isa和class指向.jpeg

addObserver之后isa和class指向

isa指向NSKVONotifying_Person

class指向Person

addObserver之后isa和class指向.jpeg

以上說明了NSKVONotifying_Person的class實(shí)際上指向了Person class,達(dá)到無(wú)入侵的目的,即看起來(lái)都是Person

5.3 setter分析
通過上面的KVO的目的和效果我們知道,執(zhí)行NSKVONotifying_Person 對(duì)應(yīng)屬性的setter方法

self.m_person.nickName = @"大神"([NSKVONotifying_Person setter])

等同于下面兩個(gè)方法

self.m_person.nickName = @"大神"([Person setter]) + KVO回調(diào)方法

那么很顯然,[NSKVONotifying_Person setter]里面對(duì)父類的setter方法發(fā)送了消息,同時(shí)對(duì)觀察者也發(fā)送了消息,也就是執(zhí)行了回調(diào)方法

下面通過代碼和斷點(diǎn)調(diào)試來(lái)驗(yàn)證一下

Person nickName的setter方法

- (void)setNickName:(NSString *)nickName {
    NSLog(@"Person setNickName");
    _nickName = nickName;
}

KVO的回調(diào)方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"change[NSKeyValueChangeNewKey] == %@", change[NSKeyValueChangeNewKey]);
    NSLog(@"person change == %@", change);
}

touchBegan對(duì)self.m_person.nickName賦值,實(shí)際上是調(diào)用[NSKVONotifying_Person setNickName:]方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    count++;
    self.m_person.nickName = [NSString stringWithFormat:@"%@+%lu", @"nickName", (unsigned long)count];
}

對(duì)self.m_person.nickName = @"大神"賦值,觸發(fā)[NSKVONotifying_Person setNickName:]方法,先對(duì)父類Person發(fā)送了setter消息,然后,對(duì)觀察者發(fā)送了KVO回調(diào)消息

先對(duì)父類Person發(fā)送了setter消息

先對(duì)父類Person發(fā)送setter消息.jpeg

再對(duì)觀察者發(fā)送了KVO回調(diào)消息

再對(duì)觀察者發(fā)送KVO回調(diào)消息.jpeg

6.小結(jié):

1.KVO只能觀察屬性值的變化,不能觀察成員變量,原因是需要?jiǎng)?chuàng)建子類并且生成子類的setter方法

2.是生成子類的setter方法,不是重寫或者覆蓋父類的setter方法

3.不會(huì)生成getter方法,因?yàn)樽宇悰]有g(shù)etter方法,所以如果調(diào)用的話實(shí)際上是父類的getter方法

4.觀察的所有屬性,全部移除觀察者后才會(huì)解除KVO的狀態(tài),再此之前指針一直指向子類NSKVONotifying_Person

5.子類的setter方法做了兩件事,一是向父類發(fā)送setter消息,二是向觀察者發(fā)送KVO的回調(diào)消息

6. NSKVONotifying_Person一旦創(chuàng)建就會(huì)存儲(chǔ)在內(nèi)存中,但是NSKVONotifying_Person實(shí)例對(duì)象是可以釋放的,類是否存在和某個(gè)實(shí)例對(duì)象是否銷毀是兩個(gè)概念

7.子類的幾個(gè)方法
isKVOA判斷是否是KVO狀態(tài)
class 無(wú)入侵外界無(wú)察覺
dealloc釋放子類 ,如果子類dealloc執(zhí)行,還保持KVO狀態(tài),會(huì)釋放父類
setter 實(shí)際上是找到子類setter方法的IMP作為入口發(fā)送了兩個(gè)消息

7.示意圖

iOS系統(tǒng)KVO原理圖5.jpg
最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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