Objective-C 学习笔记 | KVO(key-value obsereving)
- Objective-C 学习笔记 | KVO(key-value obsereving)
- 使用 KVO
- KVO 的工作原理
Objective-C 学习笔记 | KVO(key-value obsereving)
KVO 是指当指定的对象的属性被修改时,允许对象接收通知的机制。
使用 KVO
创建一个 BNRObserver,实现 lastTime 发生变化时的回调方法:
#import "BNRObserver.h"
@implementation BNRObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"Observed: %@ of %@ was changed from %@ to %@", keyPath, object, oldValue, newValue);
}
@end
在 main.m 中创建一个 BNRObserver 实例,让它观察 logger 的 lastTime 属性:
__unused BNRObserver *observer = [[BNRObserver alloc] init];
// 一旦 logger 的 lastTime 发生变化,通知 observer 改变的新值和之前的旧值
[logger addObserver:observer forKeyPath:@"lastTime"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
如果使用存取方法来设置属性,那么系统会自动通知观察者,上述方法有效。
如果直接给实例变量赋值,可以通过 willChangeValueForKey: 和 didChangeValueForKey: 方法显式通知系统某个属性的值即将/已经改变。
// 动作方法总有一个实参,它是传入发送动作消息的对象
- (void)updateLastTime:(NSTimer *)timer
{
NSDate *now = [NSDate date];
// [self setLastTime:now];
[self willChangeValueForKey:@"lastTime"];
_lastTime = now;
[self didChangeValueForKey:@"lastTime"];
NSLog(@"Just set time to %@", self.lastTimeString);
}
如果不观察 _lastTime,而是观察 _lastTimeString,我们可以实现一个类方法,告诉系统 _lastTime 会影响 _lastTimeString:
// 类方法的名称 = keyPathsForValuesAffecting + 首字母大写的键的名称(LastTimeString)
+ (NSSet *)keyPathsForValuesAffectingLastTimeString
{
return [NSSet setWithObject:@"lastTime"];
}
KVO 的工作原理
运行时,如果向某个对象发送 addObserver:forKeyPath:options:context:
方法,它会完成下面三件事:
- 决定被观察对象的类,并使用 objc_allocateClassPair() 函数给这个类定义一个新的子类。
- 改变对象的 isa 指针,让它指向新的子类(高效改变对象的类型)。
- 覆盖被观察对象的存取器,发送 KVO 消息。
例如,一个类的 location 属性的存方法代码如下:
// BNRTowel
- (void)setLocation:(NSPoint)location
{
_location = location;
}
在新的子类中,该方法会被覆盖为:
// KVONotifying_BNRTowel
- (void)setLocation:(NSPoint)location
{
[self willChangeValueForKey:@"location"];
[super setLocation:location];
[self didChangeValueForKey:@"location"];
}
子类的存取器实现会调用原始类的实现,然后将它们用简明的 KVO 通知消息封装起来。这些新的类以及方法都会在运行时使用 Objective-C 运行时函数定义。