文章目录
使用
/// 创建Person类
@interface DDPerson : NSObject
@property (nonatomic, assign) int age;
@end
#import "ViewController.h"
#import "DDPerson.h"
@interface ViewController ()
@property (nonatomic, strong) DDPerson *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[DDPerson alloc] init];
self.person.age = 10;
/// 添加KVO
NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:option context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ -- %@", keyPath, change);
/* 打印结果
age -- {
kind = 1;
new = 20;
old = 10;
}
*/
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person.age = 20;
}
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"age"];
}
@end
底层原理
当我们创建给类的属性添加KVO时,程序是如何执行的呢,接下来我们一起探讨下。
/// 首先我们创建Person的两个实例对象
Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];
// 我们给person1添加KVO
NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[person1 addObserver:self forKeyPath:@"age" options:option context:nil];
/// 如果我们同时修改person1和person2的age值
person1.age = 20;
person2.age = 20;
如果我们运行程序的话可以发现,person1监听到了age改变,person2没有监听到改变。我们知道,实例对象的isa指向的是类对象,但上方两个对象都是对age属性进行了修改,那么为什么没有走监听呢,带着这个疑问我们尝试打印一下两个实例对象的类对象。如下
// 接下来我们打印一下两个实例对象的类对象
NSLog(@"person1 ---------- %@", object_getClass(self.person1));
NSLog(@"person2 ---------- %@", object_getClass(self.person2));
/// 打印结果:NSKVONotifying_DDPerson
/// 打印结果:DDPerson
由此我们可以看到 person1 对应的类是NSKVONotifying_DDPerson,而 person2对应的才是DDPerson。
KVO实现流程:如果我们对一个实例对象的属性设置kvo时,OC会动态创建一个继承自类对象的类对象,并且让该实例对象的isa指向新创建的类对象。而没有添加KVO的实例变量的isa直接指向自己对象的类对象。如下图
KVO内部修改值过程
当我们修改成员变量的值的时候,此时会调用新创建的这个类对象的set方法。这个set方法实际调用的是Foundation框架中的_NSSetIntValueAndNotify函数。这个函数会做三件事,首先会调用willChangeValueForKey: 然后调用父类对象中的set方法给属性赋值,接线来调用的是didChangeValueForKey:方法,这个方法的内部内部会调用observer的observeValueForKeyPath:ofObject:change:context:。
⚠️⚠️⚠️ 注意:_NSSetIntValueAndNotify 这个方法不是固定的,如果属性是double类型,那么会调用_NSSetDoubleValueAndNotify。也就是会调用_NSSetXXXValueAndNotify,XXX就是属性类型
总结
当给实例对象的属性添加KVO后
- OC会动态创建NSKVOAndNotifying_DDPerson类对象
- 调用NSKVOAndNotifying_DDPerson对象中的set方法
- set方法自动调用Foundation中的_NSSetXXXValueAndNotify方法
- _NSSetXXXValueAndNotify内部调用willChangeValueForKey:
- 调用原来的setter方法
- 调用didChangeValueForKey:
- didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法
补充——手动触发KVO
想要手动触发KVO,那么就得搞清楚流程,触发KVO其实调用是setter方法。我们只需把setter方法内部实现以下就行了。所以我们只需手动调用willChangeValueForKey: 和didChangeValueForKey: 两个方法就行了
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.person1 willChangeValueForKey:@"age"];
[self.person1 didChangeValueForKey:@"age"];
/// 这样的话 控制台依然会打印
/*
age -- {
kind = 1;
new = 10;
old = 10;
}
*/
}
完结撒花,如果有误,欢迎指正。大家加油