KVO
什么是KVO
KVO的全称是Key-Value Observing
,俗称“键值监听”,可以用于监听某个对象属性值的改变
KVO的用法
self.person1 = [[MyPerson alloc] init];
self.person1.age = 1;
self.person1.height = 11;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
// 页面销毁时,移除监听对象
- (void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
}
// 打印输入:
监听到<MyPerson: 0x600002390680>的age属性值改变了 - {
kind = 1;
new = 20;
old = 1;
} - 123
KVO的本质
当给对象添加KVO监听后,po self.person1 ->isa
查看对象的isa指针,这时候会变为NSKVONotifying_MyPerson
;和没添加KVO监听前的isa指针的对象发生了变化
未使用KVO监听的对象,调用属性set方法时
调用对象属性set方法时,会通过isa指针找到类对象,在类对象里面直接找set方法的实现
使用KVO监听的对象,调用属性set方法时
调用对象属性set方法时,会通过isa指针找到NSKVONotifying_MyPerson
,然后在NSKVONotifying_MyPerson
对象里面找set方法实现,set方法会调用Foundation
框架的NSSetIntValueAndNotify
验证是否存在
NSKVONotifying_MyPerson
类,可手动创建一个相同名字的NSKVONotifying_MyPerson,这时候运行项目调用对象set方法,编译器就会有警告输出:KVO failed to allocate class pair for name NSKVONotifying_MyPerson, automatic key-value observing will not work for this class
这也从侧面证明了KVO动态生成了这样一个类对象
通过lldb指令查看IMP,可以很明显的看出KVO最终的调用方法:
// ----------------打印添加了KVO的对象,看看类对象和元类对象分别是什么
NSLog(@"类对象 - %@ %@",
object_getClass(self.person1), // self.person1.isa
object_getClass(self.person2)); // self.person2.isa
NSLog(@"元类对象 - %@ %@",
object_getClass(object_getClass(self.person1)), // self.person1.isa.isa
object_getClass(object_getClass(self.person2))); // self.person2.isa.isa
// --------输出-------
类对象 - NSKVONotifying_MyPerson MyPerson
元类对象 - NSKVONotifying_MyPerson MyPerson
// ---因此添加了KVO后,类对象会变为NSKVONotifying_MyPerson,NSKVONotifying_MyPerson的isa指针指向同一个元类对象
KVO原理
苹果使用了isa混写技术
(isa-swizzling)来实现KVO,当我们调用addObserver:forKeyPath:options:context:
方法时,系统会为我们监听的instance对象
用Runtime api
动态创建一个NSKVONotifying_XXXX
子类,并且让这个监听的instance对象
的isa指针,指向这个全新的子类,并且重写被监听的类属性的setter
方法来达到可以通知所有观察者对象的目的
当移除KVO监听后,被观察对象的
isa
会指回原类
A,但是NSKVONotifying_XXXX
类并没有销毁,还保存在内存中。
_NSSet*ValueAndNotify的内部实现
- 调用willChangeValueForKey:
- 调用原来的setter实现
- 调用didChangeValueForKey:
- didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法
FBKVOController
系统的KVO使用起来比较麻烦,需要添加监听、实现监听、移除监听
FBKVOController
是 Facebook
开源的一个基于系统KVO
实现的框架。支持Objective-C
和Swift
语言
github: https://github.com/facebook/KVOController
willChange和didChange的不同点
willChange
接下来会调用setter
方法,而didChange
接下来会调用对象的observeValueForKeyPath:ofObject:change:context:
方法
FBKVOController 的优点
- 会自动移除观察者
- 函数式编程,一行代码实现KVO监听的三个步骤
- 线程安全
- 每一个keypath会对应一个block或者SEL,不需要使用if判断keypath
使用方法:
// create KVO controller with observer
FBKVOController *KVOController = [FBKVOController controllerWithObserver:self];
self.KVOController = KVOController;
// observe clock date property
// 使用 block
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {
// update clock view with new value
clockView.date = change[NSKeyValueChangeNewKey];
}];
// 使用 SEL
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew action:@selector(updateClockWithDateChange:)];
KVC
什么是KVC
KVC
的全称是Key-Value Coding
,俗称“键值编码”,可以通过一个key来访问某个属性
KVC的用法
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
通过KVC修改属性会触发KVO么?
能够触发
KVC setValue:forKey:的原理
KVC valueForKey:的原理
本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.