回顧
在上一篇博客中善茎,已經(jīng)介紹了KVO
的相關(guān)操作毫缆,那么接下來就去探索一下KVO
的底層邏輯悼吱,KVO
到底是如何實現(xiàn)的呢堰酿?
-
在官方文檔中有如下圖中的說明
isa-swizzling
鍵值觀察是使用稱為
isa-swizzling
的技術(shù)實現(xiàn)的。
該
isa
指針昭卓,顧名思義愤钾,指向?qū)ο蟮念悾3忠粋€調(diào)度表候醒。該調(diào)度表主要包含指向類實現(xiàn)的方法的指針绰垂,以及其他數(shù)據(jù)。當觀察者為對象的屬性注冊時火焰,被觀察對象的
isa
指針被修改劲装,指向中間類
而不是真正的類
。因此,isa
指針的值不一定反映實例的實際類占业。你不應該依賴
isa
指針來確定類的成員绒怨。相反,應該使用該class
方法來確定實例對象的類谦疾。
1. isa-swizzling驗證
在添加觀察者處南蹂,打上斷點,再控制臺lldb
調(diào)試看看念恍。
- (void)viewDidLoad {
[super viewDidLoad];
self.student = [[JPStudent alloc]init];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}
-
控制臺打印如下
lldb調(diào)試
在
addObserver
后student
從JPStudent
變成了NSKVONotifying_JPStudent
我們知道六剥,實例對象
和類
的關(guān)系實際上就是實例對象的isa
指向了類對象
。所以這里我們可以推斷峰伙,self.student
在調(diào)用addObserver
方法后疗疟,已經(jīng)從JPStudent
類的實例對象,變成了NSKVONotifying_JPStudent
的實例對象瞳氓。
2. NSKVONotifying_JPStudent子類驗證
- 那么這個
NSKVONotifying_JPStudent
是什么東西呢策彤?是一開始就直接存在,還是和JPStudent
類之間有什么關(guān)系呢匣摘?那么看看NSKVONotifying_JPStudent
是不是一開始就存在的店诗,再次運行代碼,斷點還是斷在添加觀察者者處音榜,打印一下
NSKVONotifying_JPStudent生產(chǎn)測試
提醒
:objc_getClass
是runtime
的api
庞瘸,一定要導入頭文件才可以正常使用,如圖所示赠叼。
在調(diào)用addObserver
方法前后分別打印擦囊,結(jié)果說明NSKVONotifying_JPStudent
是系統(tǒng)動態(tài)生成添加的一個類。這兩個類名字這么相似梅割,有沒有可能是JPStudent
的子類呢霜第?我們打印一下看看
從打印來看葛家,發(fā)現(xiàn)了新大陸户辞,
NSKVONotifying_JPStudent
確實是繼承自JPStudent
的。那么這個中間類癞谒,有沒有可能存在自己的子類呢底燎?我們通過下面這段代碼來看看
#pragma mark - 遍歷類以及子類
- (void)printClasses:(Class)cls{
// 注冊類的總數(shù)
int count = objc_getClassList(NULL, 0);
// 創(chuàng)建一個數(shù)組, 其中包含給定對象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 獲取所有已注冊的類
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);
}
- 打印結(jié)果如下
遍歷類以及子類
從打印來看弹砚,可以驗證NSKVONotifying_JPStudent
是JPStudent
的子類双仍。
那么NSKVONotifying_JPStudent
這個類里面都有些什么內(nèi)容呢?類里面一般也就是存儲了成員變量
桌吃、方法
朱沃、協(xié)議
等信息,那么通過下面這段代碼來看看它里面都有什么。
#pragma mark **- 遍歷方法-ivar-property**
- (void)printClassAllMethod:(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);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
-
打印如下
遍歷方法-ivar-property
從打印結(jié)果來看逗物,系統(tǒng)重寫了
setNickName
搬卒、class
、dealloc
這幾個方法翎卓,并且添加了一個叫_isKVOA
的方法契邀,來區(qū)分是不是系統(tǒng)通過KVO
自動生成的。
3. 觀察者被移除isa的指向失暴?
官方文檔中說:調(diào)用addObserver
方法會修改isa
指向坯门,那么現(xiàn)在我們移除觀察者后系統(tǒng)會怎么做呢?我們在dealloc
方法中移除觀察者這里打上斷點逗扒,然后繼續(xù)觀察self.student
的isa
指向古戴。
移除觀察者之后缴阎,
self.student
的isa
又指回了JPStudent
類允瞧。并且生成的子類NSKVONotifying_JPStudent
還在,沒有進行銷毀蛮拔。原因是如果下次繼續(xù)進行觀察者添加述暂,系統(tǒng)就不會再生成新的中間類,而是直接使用這個類了建炫,防止資源的浪費畦韭。
4. class方法
在添加觀察者之后,我們都知道會生成動態(tài)子類NSKVONotifying_JPStudent
肛跌,
那么調(diào)用
class
方法p self.student.class
打印的是NSKVONotifying_JPStudent
嗎艺配??衍慎?
- 斷點在添加觀察者之后转唉,我們控制臺驗證一下
self.student.class
從打印結(jié)果看,輸出的還是JPStudent
稳捆,雖然self.student
的isa
已經(jīng)指向NSKVONotifying_JPStudent
了赠法,但是由于NSKVONotifying_JPStudent
重寫了class
方法,最后打印輸出的還是JPStudent
乔夯,蘋果這么做的目的是為了隱藏系統(tǒng)在背后做的一系列操作砖织,讓開發(fā)者更少的關(guān)注底層邏輯,只關(guān)注上層的代碼實現(xiàn)就可以末荐。
5. setter方法
既然重寫了setter
方法觀察屬性侧纯,那么如果有成員變量,是否也可以能觀察呢甲脏?增加age
成員變量眶熬,測試一下
@interface JPStudent : NSObject
{
@public
int age;
}
@property (nonatomic, copy) NSString *name;
@end
當對
age
進行賦值的時候妹笆,并沒有觸發(fā)監(jiān)聽的回調(diào)方法。那么就說明了只是對屬性的setter
方法進行的監(jiān)聽娜氏。
我們再看看在dealloc
中觀察者移除isa
指回的時候晾浴,查看name
的值
那就說明在
KVO
生成的類中對name
的修改影響到了原始類。
對name
下個內(nèi)存斷點調(diào)試看看
-
bt 打印堆棧信息
bt 打印堆棧信息
發(fā)現(xiàn)調(diào)用了
Foundation
的一些方法牍白,最后才是[JPStudent setName:]
給name
賦值脊凰。
提示
:Foundation
框架是不開源的。
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
- Foundation`_NSSetObjectValueAndNotify
_NSSetObjectValueAndNotify
匯編調(diào)用主要如下:
"willChangeValueForKey:"
這里是調(diào)用setter方法賦值
"didChangeValueForKey:"
"_changeValueForKey:key:key:usingBlock:"
從堆棧信息和匯編可以知道茂腥,在
_changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:
中進行賦值后的回調(diào)狸涌,那么肯定得通知監(jiān)聽者
- 在
observeValueForKeyPath
的回調(diào)中打個斷點:
確認是在
NSKeyValueNotifyObserver
通知中進行的回調(diào)。
6.總結(jié)
-
KVO
添加觀察者addObserver
動態(tài)生成子類NSKVONotifying_XXX
最岗。 - 重寫
class
方法帕胆,返回父類class
信息。父類isa
指向子類般渡。
給動態(tài)子類添加setter
方法(所有要觀察的屬性)懒豹。
消息轉(zhuǎn)發(fā)給父類。 -
setter
會調(diào)用父類原來的方法進行賦值驯用,完成后進行回調(diào)通知脸秽。 - 移除
observer
的時候isa
指回父類,動態(tài)生成的子類并不會銷毀.
更多內(nèi)容持續(xù)更新
?? 喜歡就點個贊吧????
?? 覺得有收獲的蝴乔,可以來一波记餐,收藏+關(guān)注,評論 + 轉(zhuǎn)發(fā)薇正,以免你下次找不到我????
??歡迎大家留言交流片酝,批評指正,互相學習??挖腰,提升自我??