使用KVO
自动触发KVO
在平日代码中,我们通过KVO来监视实例某个属性的变化。
比如,我们要监视Student 的 age属性,可以这么做:
@interface Student : NSObject
@property(nonatomic, strong) NSString *name;
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *std = [Student new];
std.name = @"Tom";
[std addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
...
}
@end
我们使用KVO需要遵循以下步骤:
- 调用
addObserver:forKeyPath:options:context:
方法来注册观察者,观察者可以接收到KeyPath对应属性的修改通知 - 当观察的属性发生变化时,系统会在
observeValueForKeyPath:ofObject:change:context:
方法中回调观察者 - 当观察者不需要监听变化是,需要调用
removeObserver:forKeyPath:
将KVO
移除。需要注意的是,在观察者被释放前,必须要调用removeObserver:forKeyPath:
将其移除,否则会crash。同时,作为被观察者,当其自身dealloc时,必须清空他的观察者,否则会crash。
手动触发KVO
当我们设置了观察者后,当被观察的keyPath对应的setter方法调用后,则会自动的触发KVO的回调函数。那么,有时候我们想要控制这种自动触发的机制,该怎么办呢?你可以重写如下方法:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
automaticallyNotifiesObserversForKey
方法声明在NSObject的Category NSObject(NSKeyValueObservingCustomization)
中。
除了在setter方法中,有时候我们想主动触发一下KVO,该怎么办呢?
那就需要使用
willChangeValueForKey:
didChangeValueForKey:
来通知系统Key Value发生了改变。如:
- (void)updateName:(NSString *)name {
[self willChangeVauleForKey:@"name"];
_name = name;
[self didChangeVauleForKey:@"name"];
}
KVO实现机制
那么,KVO背后是如何实现的呢?在苹果的官方文档上,有一个笼统的描述:
Automatic key-value observing is implemented using a technique called
isa-swizzling.The isa pointer, as the name suggests, points to the object’s class
which maintains a dispatch table. This dispatch table essentially
contains pointers to the methods the class implements, among other
data.When an observer is registered for an attribute of an object the isa
pointer of the observed object is modified, pointing to an
intermediate class rather than at the true class. As a result the
value of the isa pointer does not necessarily reflect the actual class
of the instance.You should never rely on the isa pointer to determine class
membership. Instead, you should use the class method to determine the
class of an object instance.
主要说了两件事:
- KVO是基于
isa-swizzling
技术实现的。isa-swizzling
会将被观察对象的isa指针
进行替换。 - 因为在实现
KVO
时,系统会替换掉被观察对象的isa指针
,因此,不要使用isa指针
来判断类的关系,而应该使用class
方法。
为什么要替换掉isa指针