Objective-C KVO簡(jiǎn)單探索
KVO(Key Value Observing),是觀察者模式在Foundation中的實(shí)現(xiàn)。
KVO的原理
簡(jiǎn)而言之就是:
1、當(dāng)一個(gè)object有觀察者時(shí),動(dòng)態(tài)創(chuàng)建這個(gè)object的類的子類
2、對(duì)于每個(gè)被觀察的property,重寫其set方法
3、在重寫的set方法中調(diào)用- willChangeValueForKey:和- didChangeValueForKey:通知觀察者
4、當(dāng)一個(gè)property沒有觀察者時(shí),刪除重寫的方法
5、當(dāng)沒有observer觀察任何一個(gè)property時(shí),刪除動(dòng)態(tài)創(chuàng)建的子類
空說無(wú)憑,簡(jiǎn)單驗(yàn)證下。
- @interface Sark : NSObject
- @property (nonatomic, copy) NSString *name;
- @end
- @implementation Sark
- @end
- Sark *sark = [Sark new];
- // breakpoint 1
- [sark addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
- // breakpoint 2
- sark.name = @"薩薩薩";
- [sark removeObserver:self forKeyPath:@"name"];
- // breakpoint 3
斷住后分別使用- class和object_getClass()打出sark對(duì)象的Class和真實(shí)的Class
- // breakpoint 1
- (lldb) po sark.class
- Sark
- (lldb) po object_getClass(sark)
- Sark
- // breakpoint 2
- (lldb) po sark.class
- Sark
- (lldb) po object_getClass(sark)
- NSKVONotifying_Sark
- // breakpoint 3
- (lldb) po sark.class
- Sark
- (lldb) po object_getClass(sark)
- Sark
上面的結(jié)果說明,在sark對(duì)象被觀察時(shí),framework使用runtime動(dòng)態(tài)創(chuàng)建了一個(gè)Sark類的子類 NSKVONotifying_Sark,而且為了隱藏這個(gè)行為,NSKVONotifying_Sark重寫了- class方法返回之前的類,就好像什么也沒發(fā)生過一樣。但是使用object_getClass()時(shí)就暴露了,因?yàn)檫@個(gè)方法返回的是這個(gè)對(duì)象的isa 指針,這個(gè)指針指向的一定是個(gè)這個(gè)對(duì)象的類對(duì)象
然后來偷窺一下這個(gè)動(dòng)態(tài)類實(shí)現(xiàn)的方法,這里請(qǐng)出一個(gè)NSObject的擴(kuò)展NSObject+DLIntrospection,它封裝了打印一個(gè)類的方法、屬性、協(xié)議等常用調(diào)試方法,一目了然。
- @interface NSObject (DLIntrospection)
- + (NSArray *)classes;
- + (NSArray *)properties;
- + (NSArray *)instanceVariables;
- + (NSArray *)classMethods;
- + (NSArray *)instanceMethods;
- + (NSArray *)protocols;
- + (NSDictionary *)descriptionForProtocol:(Protocol *)proto;
- + (NSString *)parentClassHierarchy;
- @end
然后繼續(xù)在剛才的斷點(diǎn)處調(diào)試:
- // breakpoint 1
- (lldb) po [object_getClass(sark) instanceMethods]
- <__NSArrayI 0x8e9aa00>(
- - (void)setName:(id)arg0 ,
- - (void).cxx_destruct,
- - (id)name
- )
- // breakpoint 2
- (lldb) po [object_getClass(sark) instanceMethods]
- <__NSArrayI 0x8d55870>(
- - (void)setName:(id)arg0 ,
- - (class)class,
- - (void)dealloc,
- - (BOOL)_isKVOA
- )
- // breakpoint 3
- (lldb) po [object_getClass(sark) instanceMethods]
- <__NSArrayI 0x8e9cff0>(
- - (void)setName:(id)arg0 ,
- - (void).cxx_destruct,
- - (id)name
- )
首先就有個(gè)扎眼的- .cxx_destruct冒出來,這貨是個(gè)啥?詳細(xì)的探究請(qǐng)參考我的另一篇文章。
大概就是說arc下這個(gè)方法在所有dealloc調(diào)用完成后負(fù)責(zé)釋放所有的變量,當(dāng)然這個(gè)和KVO沒啥關(guān)系了,回到正題。
從上面breakpoint2的打印可以看出,動(dòng)態(tài)類重寫了4個(gè)方法:
1、- setName:最主要的重寫方法,set值時(shí)調(diào)用通知函數(shù)
2、- class隱藏自己必備啊,返回原來類的class
3、- dealloc做清理犯罪現(xiàn)場(chǎng)工作
4、- _isKVOA這就是內(nèi)部使用的標(biāo)示了,判斷這個(gè)類有沒被KVO動(dòng)態(tài)生成子類
接下來驗(yàn)證一下KVO重寫set方法后是否調(diào)用了- willChangeValueForKey:和- didChangeValueForKey:
最直接的驗(yàn)證方法就是在Sark類中重寫這兩個(gè)方法:
- @implementation Sark
- - (void)willChangeValueForKey:(NSString *)key
- {
- NSLog(@"%@", NSStringFromSelector(_cmd));
- [super willChangeValueForKey:key];
- }
- - (void)didChangeValueForKey:(NSString *)key
- {
- NSLog(@"%@", NSStringFromSelector(_cmd));
- [super didChangeValueForKey:key];
- }
- @end



















