Objective-C KVO簡單探索
KVO(Key Value Observing),是觀察者模式在Foundation中的實現(xiàn)。
KVO的原理
簡而言之就是:
1、當(dāng)一個object有觀察者時,動態(tài)創(chuàng)建這個object的類的子類
2、對于每個被觀察的property,重寫其set方法
3、在重寫的set方法中調(diào)用- willChangeValueForKey:和- didChangeValueForKey:通知觀察者
4、當(dāng)一個property沒有觀察者時,刪除重寫的方法
5、當(dāng)沒有observer觀察任何一個property時,刪除動態(tài)創(chuàng)建的子類
空說無憑,簡單驗證下。
- @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對象的Class和真實的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對象被觀察時,framework使用runtime動態(tài)創(chuàng)建了一個Sark類的子類 NSKVONotifying_Sark,而且為了隱藏這個行為,NSKVONotifying_Sark重寫了- class方法返回之前的類,就好像什么也沒發(fā)生過一樣。但是使用object_getClass()時就暴露了,因為這個方法返回的是這個對象的isa 指針,這個指針指向的一定是個這個對象的類對象
然后來偷窺一下這個動態(tài)類實現(xiàn)的方法,這里請出一個NSObject的擴展NSObject+DLIntrospection,它封裝了打印一個類的方法、屬性、協(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à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
- )
首先就有個扎眼的- .cxx_destruct冒出來,這貨是個啥?詳細的探究請參考我的另一篇文章。
大概就是說arc下這個方法在所有dealloc調(diào)用完成后負責(zé)釋放所有的變量,當(dāng)然這個和KVO沒啥關(guān)系了,回到正題。
從上面breakpoint2的打印可以看出,動態(tài)類重寫了4個方法:
1、- setName:最主要的重寫方法,set值時調(diào)用通知函數(shù)
2、- class隱藏自己必備啊,返回原來類的class
3、- dealloc做清理犯罪現(xiàn)場工作
4、- _isKVOA這就是內(nèi)部使用的標(biāo)示了,判斷這個類有沒被KVO動態(tài)生成子類
接下來驗證一下KVO重寫set方法后是否調(diào)用了- willChangeValueForKey:和- didChangeValueForKey:
最直接的驗證方法就是在Sark類中重寫這兩個方法:
- @implementation Sark
- - (void)willChangeValueForKey:(NSString *)key
- {
- NSLog(@"%@", NSStringFromSelector(_cmd));
- [super willChangeValueForKey:key];
- }
- - (void)didChangeValueForKey:(NSString *)key
- {
- NSLog(@"%@", NSStringFromSelector(_cmd));
- [super didChangeValueForKey:key];
- }
- @end