KVO 解密一:KVO简单使用
KVO
全称KeyValueObserving
,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接受到事件。一般继承自NSObject的对象默认都支持KVO
。
KVO
和NSNotificationCenter
都是iOS观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO是一对一的,而不是一对多的。KVO对被监听的对象没有侵入性,不需要修改其内部代码即可实现监听。
基础使用
使用KVO分为三个步骤:
- 通过addObserver:forKeyPath:options:context:方法注册观察者
- 在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生变化时,KVO会调用这个方法通知观察者。
- 当观察者不需要监听时,调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要再观察者消失以前,否则会导致Crash。
简单调用举例:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.p = [[Person alloc]init];
self.p.name = @"ios";
self.p.age = 10;
[self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
self.p.name = @"andriod";
self.p.age = 8;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@",change);
NSLog(@"new = %@",change[NSKeyValueChangeNewKey]);
NSLog(@"old = %@",change[NSKeyValueChangeOldKey]);
}
输出结果为:
2019-04-16 21:49:29.834536+0800 KVO原理[36234:11195564] {
kind = 1;
new = andriod;
old = ios;
}
2019-04-16 21:49:29.834709+0800 KVO原理[36234:11195564] new = andriod
2019-04-16 21:49:29.834837+0800 KVO原理[36234:11195564] old = ios
KVO的简单优化
当我们把self.p.name = @"ios"
跟原来的值相同时,会发生什么现象呢?
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.p = [[Person alloc]init];
self.p.name = @"ios";
self.p.age = 10;
[self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
self.p.name = @"ios";
self.p.age = 8;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@",change);
NSLog(@"new = %@",change[NSKeyValueChangeNewKey]);
NSLog(@"old = %@",change[NSKeyValueChangeOldKey]);
}
打印输出结果:
2019-04-16 22:09:47.840181+0800 KVO原理[36296:11226374] {
kind = 1;
new = ios;
old = ios;
}
2019-04-16 22:09:47.840368+0800 KVO原理[36296:11226374] new = ios
2019-04-16 22:09:47.840455+0800 KVO原理[36296:11226374] old = ios
发现无论是值是否变化,只要调用了setter方法,都会得到通知,这样可能会造成不必要的资源浪费。我们做一个简单的小优化:只有重新赋值(不一样的值)才去通知观察者,这就需要引入新的知识了
willChangeValueForKey:
didChangeValueForKey:
当我们手动控制通知消息发送的时机是,就需要用到这两个函数,顾名思义,函数的作用就是 key value即将变化, key value已经变化
另外一个函数:+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
是否自动通知观察者,如果需要手动控制,则需要返回NO,自动控制的话返回YES
我们将程序做一个简单的修改:
//Person.m
- (void)setName:(NSString *)name {
if (![_name isEqualToString:name]) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
BOOL automatic = NO;
if ([key isEqualToString:@"age"]) {
automatic = YES;
} else if ([key isEqualToString:@"name"]) {
automatic = NO;
} else {
automatic = [super automaticallyNotifiesObserversForKey:key];
}
return automatic;
}
此时我们发现,当再次给p.name赋值为原来的值时,观察者不再收到通知事件。
总结
KVO是在setter函数调用时,给观察者发送事件通知,铭记在观察者释放之前移除KVO
至于为什么调用setter函数,就会给观察者发送时间通知?我们下次分享KVO的实现原理。
参考资料:
KVO原理分析及使用进阶
KVO进阶(二)