什么是KVO
KVO(Key-Value Observing),俗称键值监听。它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
监听方法
- (void)addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context;
- observer: 观察者,也就是KVO通知的订阅者
- keyPath: 描述将要观察的属性,相对于被观察者
- options: KVO的一些属性配置;有四个选项
- NSKeyValueObservingOptionNew:change字典包括改变后的值
- NSKeyValueObservingOptionOld:change字典包括改变前的值
- NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
- NSKeyValueObservingOptionPrior:值改变前是否也要通知
- context: 上下文,这个会传递到订阅着的函数中,用来区分消息
移除监听
- (void)removeObserver:(NSObject*)anObserver forKeyPath:(NSString*)keyPath;
- (void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath context:(void*)context;
回调监听
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context;
KVO使用步骤
- 注册,指定被观察者的属性
- 实现回调方法
- 移除观察
代码展示
@interfacePerson:NSObject
@property(nonatomic,assign)NSIntegerage;
@end
@implementationPerson
@end
self.person1 = [[Person alloc]init];
self.person2 = [[Person alloc]init];
NSLog(@"监听前%@ --- %@",object_getClass(self.person1),object_getClass(self.person2));
NSKeyValueObservingOptionsoptions =NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld;
[self.person2 addObserver:selfforKeyPath:@"age"options:options context:nil];
self.person2.age =20;
NSLog(@"监听后%@ --- %@",object_getClass(self.person1),object_getClass(self.person2));
[self.person2 removeObserver:selfforKeyPath:@"age"];
打印结果:
KVODemo[2879:697131] 监听前Person --- Person
KVODemo[2879:697131] 监听后NSKVONotifying_Person--- Person
监听前
- self.person1的isa指向Person
- self.person2的isa指向Person
监听后
- self.person1的isa指向NSKVONotifying_Person
- self.person2的isa指向Person
RuntimeAPI动态生成一个NSKVONotifying_Person,并且让instance对象的isa指向这个全新的NSKVONotifying_Person
打印NSKVONotifying_Person内部方法
我们需要用到这个分类DLIntrospection已经有大神帮我们封装好了
+ (NSArray*)classes;
+ (NSArray*)properties;
+ (NSArray*)instanceVariables;
+ (NSArray*)classMethods;
+ (NSArray*)instanceMethods;
+ (NSArray*)protocols;
+ (NSDictionary*)descriptionForProtocol:(Protocol *)proto;
+ (NSString*)parentClassHierarchy;
断点调试结果
KVO[23897:840022] 监听前Person --- Person
(lldb) po [object_getClass(self.person1) instanceMethods]
<__NSArrayI0x604000429720>(
- (void)setAge:(q)arg0 ,
- (q)age
)
(lldb) po [object_getClass(self.person2) instanceMethods]
<__NSArrayI0x600000245ca0>(
- (void)setAge:(q)arg0 ,
- (class)class,
- (void)dealloc,
- (BOOL)_isKVOA
)
KVO[23897:840022] 监听后Person ---NSKVONotifying_Person
(lldb) po [object_getClass(self.person1) instanceMethods]
<__NSArrayI0x60000022a560>(
- (void)setAge:(q)arg0 ,
- (q)age
)
(lldb) po [object_getClass(self.person2) instanceMethods]
<__NSArrayI0x6040002217a0>(
- (void)setAge:(q)arg0 ,
- (q)age
)
结论
- RuntimeAPI动态生成一个NSKVONotifying_Person,并且让instance对象的isa指向这个全新的NSKVONotifying_Person
- -setAge: 修改age会调用Foundation的_NSSetXXXValueAndNotify函数(下面有打印)
- _NSSetXXXValueAndNotify函数会调用①willChangeValueForKey:,②然后调用原来的setter方法,③最后调用didChangeValueForKey:,在内部触发监听方法( observeValueForKeyPath:ofObject:change:context:)
- 为了隐藏NSKVONotifying_Person子类,重写了class方法,返回Person(苹果真会搞事情)
- -dealloc 清理现场
- -_isKVO内部表示,判断这个类有没有动态生成子类
我们来证明一下
@implementationPerson
- (void)willChangeValueForKey:(NSString*)key {
NSLog(@"%@",NSStringFromSelector(_cmd));
[superwillChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString*)key {
NSLog(@"begin---%@",NSStringFromSelector(_cmd));
[superdidChangeValueForKey:key];
NSLog(@"end---%@",NSStringFromSelector(_cmd));
}
@end
KVO[24586:884853] willChangeValueForKey:
KVO[24586:884853] begin---didChangeValueForKey:
KVO[24586:884853] {
kind =1;
new =20;
old =0;
}
KVO[24586:884853] end---didChangeValueForKey:
网友评论