KVO 的实现原理
在 Objective-C 中,用 KVO 可以很方便的观察某个属性的值的变化,一有变化可以立刻响应,虽然滥用 KVO 容易踩坑,但是在很多情形下,KVO 还是很好用的。接下来我们来看一看 KVO 是怎么实现的。
我们来写一个例子来研究,创建一个 Simple App 项目,首先还是写一个 Student 类:
|
在viewcontroller里定义
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *b = [[Student alloc] init];
[b addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew context:nil];
self.b = b;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"name = %@", self.b.name);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static NSInteger i = 0;
i++;
self.b.name = [NSString stringWithFormat:@"%@", @(i)];
}
运行程序,每次点击屏幕后,会修改 b 对象的 name 的值,然后触发 KVO 回调,打印出 name 的值。
在 Student *b = [[Student alloc] init]; 一行设一个断点,重新运行程序,程序中断在这一行上。
让程序往下运行一步,然后在下方查看 b 对象的 isa 指针的值:
isa Class Student 0x0000000103ba15d8
这时 isa 的值是 Student 这个类。
让程序再往下走一步,再次查看 b 对象的 isa 指针:
会发现这时 b 对象的 isa 指针变为了 NSKVONotifying_Student 这个类。
因此,可以大概知道,KVO 的实现原理为:
在执行 addObserver:selector:name:object: 时,创建了一个被观察对象的子类,并重写了被观察属性的 setter 方法。
结果表明,
原始类和中间类都有 setter 方法。根据我们前面所探索的消息发送以及转发流程,这里的中间类应该是重写了 setName: 、class、 dealloc 和 _isKVOA 方法。(中间类重写的 class 方法结果仍然是返回的是原始类,显然系统这样做的目的就是隐藏中间类的存在,让调用者调用 class 方法结果前后一致。)
移除观察者之后,对象的 isa 指针已经指回了原始的类。
中间类仍然存在,也就是说移除观察者并不会导致中间类销毁,显然这样对于多次添加和移除观察者来说性能上更好。
下面来通过自己实现 KVO 来看一下具体的细节。
自己动手实现 KVO
下面我们来针对上面例子中 Student 的 name 来自己实现 KVO。下面的实现中先写死仅对 setName: 方法实现 KVO,来提供一个最简单的实现流程。
1.
创建一个 NSObject 的类别,文件为 NSObject+MyKVOImp.h 和 NSObject+MyKVOImp.m。
2.
在 NSObject+MyKVOImp.h 中,添加两个方法声明:
| |
上面两个方法用于替代系统 KVO 提供的那两个不带前缀的方法。
3.
在 NSObject+MyKVOImp.m 中添加 my_addObserver... 方法的实现:
| |
4. 现在来看看 setName 函数的实现:
|
我们知道每个 Objective-C 方法都含有两个隐式参数 self 和 _cmd,他们对应 C 函数中的第一个和第二个参数。因此 setName 的函数的第一个和第二个参数必须是 id self 和 SEL _cmd。第三个参数才是作为生成的 Objective-C 方法 setName: 的第一个参数。
这样一个简单的 KVO 就实现好了,可以在 ViewController 中调用我们自己实现的方法试试效果:
- (void)viewDidLoad {
[super viewDidLoad];
self.st = [[Student alloc] init];
[self.st my_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];}
- (void)my_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"name = %@", self.st.name);}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static NSInteger i = 0;
i++;
self.st.name = [NSString stringWithFormat:@"%@", @(i)];}结语
以上我们介绍了 KVO 的实现原理并自己实现了一个简单的 KVO,实际上 KVO 的实现还是很复杂的,要考虑到很多地方,复杂的实现网上有相关代码,或者看 KVO 源码了解一下。