探索KVO原理
有這么一個(gè)示例:
- 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 {
}
- Student類繼承自Person類:
// Student.h 什么也沒(méi)做
@interface Student : Person
@end
// Student.m 實(shí)現(xiàn)繼承來(lái)的run方法
- (void)run {
}
- 有兩個(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);
}
- 調(diào)用工具方法打印Person 和 Student的所有方法及類和子類
[self printClassAllMethod:[Person class]];
[self printClassAllMethod:[Student class]];
[self printClasses:[Person class]];
[self printClasses:[Student class]];
- 打印結(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ù)期的完全一樣《《《
- 添加一個(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,真是神奇。

- 既然有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原理:

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的本類,且KVO為NSKVONotifying_A重寫(xiě)觀察屬性的setter方法,setter方法會(huì)負(fù)責(zé)在調(diào)用原setter方法之前和之后,通知所有觀察對(duì)象屬性值的更改情況。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)。所以當(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è)中間類了。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ī)制。子類
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