KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变;
KOV的应用
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc] init];
self.person.age = 10;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ ---- %@", keyPath, change);
}
- 对象添加监听器(addObserver: forKeyPath: options: context),可设置监听旧值(Old)或新值(New);
- 重写回调方法(observeValueForKeyPath: ofObject: change: context:),就可以监听对象属性的变化;
原理
没有设置kvo的对象设置kvo的对象
- 给对象的属性设置kvo,此时runtime将创建一个类名为NSKVONotifying_×××的类(如NSKVONotifying_Person),该类为Person的子类;然后将实例对象的isa指针指向该类;
- 该类将重写已被设置kvo的setter方法(如setAge:);class方法返回的是父类的类对象(如: [Person class]);
NSKVONotifying_×××的setter方法
上图中setAge:方法,内部调用了Foundation的_NSSetIntValueAndNotify方法;伪代码如下:
- (void)setAge:(int)age
{
_NSSetIntValueAndNotify();
}
// 伪代码
void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
// 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
_NSSetIntValueAndNotify内部实现:
- 首先调用_NSSetIntValueAndNotify;
- 然后调用父类的setter方法([super setAge:age]);
- 最后调用didChangeValueForKey,该方法内部回调监听器方法( observeValueForKeyPath:ofObject:change:context:),整个监听过程完成。
_NSSet***ValueAndNotify
上述例子是对 int 属性进行监听,所以对应着_NSSetIntValueAndNotify,不同类型的属性对应不同的_NSSet×××ValueAndNotify方法;
代码验证
在Person类中,重写willChangeValueForKey和didChangeValueForKey,查看执行状态;
@implementation Person
- (void)setAge:(int)age
{
_age = age;
NSLog(@"setAge:");
}
- (void)willChangeValueForKey:(NSString *)key
{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(@"didChangeValueForKey - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey - end");
}
@end
2019-07-15 19:17:29.235721+0800 KVO[5624:7935847] willChangeValueForKey
2019-07-15 19:17:29.235880+0800 KVO[5624:7935847] setAge:
2019-07-15 19:17:29.235975+0800 KVO[5624:7935847] didChangeValueForKey - begin
2019-07-15 19:17:29.236946+0800 KVO[5624:7935847] age ---- {
kind = 1;
new = 20;
old = 10;
}
2019-07-15 19:17:29.237125+0800 KVO[5624:7935847] didChangeValueForKey - end
可以看出监听回调确实是在didChangeValueForKey内部执行的。
源代码demo
网友评论