KVO
KVO全程是Key Value Observer,键值监听机制。
底层实现机制是isa-swizzing
KVO概念
键值观察Key-Value-Observer就是观察者模式
- 观察者模式定义:一个目标对象管理所有依赖于它的观察者对象,并在它自身状态改变时主动通知观察者对象。这个主动通知通常是通过调用观察者对象所提供的接口方法来实现的。观察者模式比较完美的将目标对象与贯彻着对象解耦
KVO实现步骤
- 注册
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
keyPath:要观察的Key
options:观察值变化的选择
context:方便传输的数据
-
实现
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
change:存储着变化的数据,比如之前的数据,变化后的数据
-
移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
KVO实现分析
使用观察者模式需要被观察者的配合,当被观察者状态发生变化的时候,通过事先定义好的接口(协议)通知观察者。在KVO的使用中,我们并不需要向观察者添加额外代码,就能在被观察者的属性变化的时候得到通知,这是如何事先的呢?依赖于强大的Runtime机制。
实现KVO有一下几个步骤
- 当类A的对象第一次被观察的时候,系统会在运行期动态创建类A的派生类。我们称为NSKVONotifying_A
- 在派生类NSKVONotifying_A中重写A的setter方法,在NSKVONotifying_A类重写的setter方法中实现通知机制
- 类NSKVONotifying_A会重写class方法,并将自己伪装成类A。类NSKVONotifying_A还回重写dealloc方法释放资源
- 系统将所有指向类A对象的isa指针指向类NSKVONotifying_A对对象
测试代码
具体实现代码:
定义一个KvoModel
声明一个成员变量value
声明一个实例方法add对value实例变量++
@interface KvoModel : NSObject
@property (nonatomic,assign) NSInteger value;
-(void)add;
@end
@implementation KvoModel
-(void)add
{
_value ++;
}
测试程序:
KvoModel *model = [[KvoModel alloc] init];
NSLog(@"init className = %s",object_getClassName(model));
[model addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"addObserver className = %s",object_getClassName(model));
model.value = 1;
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if([object isKindOfClass:[KvoModel class]] && [keyPath isEqualToString:@"value"])
{
NSLog(@"kvo new value = %@",change[@"new"]);
}
}
通过执行上述代码可以看到输出:
KsObjcTestDemo[4113:3118713] init className = KvoModel
KsObjcTestDemo[4113:3118713] addObserver className = NSKVONotifying_KvoModel
KsObjcTestDemo[4113:3118713] kvo new value = 1
可以看到当我们注册观察value
属性时 model的class是有所改变的
我们试试利用KVC修改属性的指是否会被检测到
KvoModel *model = [[KvoModel alloc] init];
NSLog(@"init className = %s",object_getClassName(model));
[model addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"addObserver className = %s",object_getClassName(model));
model.value = 1;
[model setValue:@2 forKey:@"value"];
查看打印结果:
KsObjcTestDemo[4113:3118713] init className = KvoModel
KsObjcTestDemo[4113:3118713] addObserver className = NSKVONotifying_KvoModel
KsObjcTestDemo[4113:3118713] kvo new value = 1
KsObjcTestDemo[4119:3121137] kvo new value = 2
可以看到,通过KVC也是可以被监控到的
我们再试试内部修改实例属性是否被检测到
KvoModel *model = [[KvoModel alloc] init];
NSLog(@"init className = %s",object_getClassName(model));
[model addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"addObserver className = %s",object_getClassName(model));
model.value = 1;
[model setValue:@2 forKey:@"value"];
[model add];
我们调用一下内部的add
方法查看打印结果
KsObjcTestDemo[4113:3118713] init className = KvoModel
KsObjcTestDemo[4113:3118713] addObserver className = NSKVONotifying_KvoModel
KsObjcTestDemo[4113:3118713] kvo new value = 1
KsObjcTestDemo[4119:3121137] kvo new value = 2
还是这个结果,我们并没有检测到add
方法里面的_value++
这是因为_value++
方法并没有调用setter方法,所有没有办法检测到,进一步的验证了我们的理论
那么我们如何来让kvo监听到呢?
我们再add
方法中加入两行代码
-(void)add
{
[self willChangeValueForKey:@"value"];
_value ++;
[self didChangeValueForKey:@"value"];
}
再次执行,查看打印结果:
KsObjcTestDemo[4113:3118713] init className = KvoModel
KsObjcTestDemo[4113:3118713] addObserver className = NSKVONotifying_KvoModel
KsObjcTestDemo[4113:3118713] kvo new value = 1
KsObjcTestDemo[4119:3121137] kvo new value = 2
KsObjcTestDemo[4124:3122591] kvo new value = 3
我们对add
方法的修改被监听到了~