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

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

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

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

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è)方法

觀察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)了子類和父類

5.3 class分析
addObserver之前isa和class指向
isa指向Person
class指向Person

addObserver之后isa和class指向
isa指向NSKVONotifying_Person
class指向Person

以上說明了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ì)觀察者發(fā)送了KVO回調(diào)消息

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.示意圖
