一.KVO基础
KVO的全称是Key-Value Observing,俗称键值监听
,可以用于监听某个对象属性值的改变
通过
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
方法给类的某个对象添加监听。
在监听类中实现
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary
进行监听
observer
:监听方
keyPath
:监听对象
options
:NSKeyValueObservingOptionNew
(提供更改前的值) NSKeyValueObservingOptionOld
(提供更改后的值)
NSKeyValueObservingOptionInitial
(观察最初的值,再注册观察服务时会调用一次触发方法)
NSKeyValueObservingOptionPrior
(分别在值修改前后触发方法,即一次修改有两次触发)
context
:传入的内容
示例:
self.person1 = [[GYPerson alloc] init];
self.person1.age = 10;
[self.person1 addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:@"123"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@ - %@ - %@ - %@", keyPath, object, change, context);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.person1.age = 100;
}
运行结果:
二.KVO剖析
我们重新定义一个GYPerson
的对象person2
,不对person2
中的任何属性进行监听。分别打印person1
与person2
的isa
指针来观察他们的类
可以观察到他们的所属的类已经不一样的,添加监听的
person1
属于
NSKVONotifying_GYPerson
类,未添加监听的
person2
仍属于
GYPerson
类。
或者
我们使用
RunTime
的
object_getClass
方法查看
NSLog(@"person1的Class%@", object_getClass(self.person1));
NSLog(@"person2的Class%@", object_getClass(self.person2));
如图:
同样说明刚刚的验证
原因:在对一个类的对象添加监听后,系统会利用Runtime
API动态生成一个子类
,并且让instance
对象的isa
指向这个全新的子类。全新的子类中重新实现了setAge:
方法,setAge:
方法实现了foundation
中的_NSSetIntValueAndNotify
方法
验证:
1.重写setAge:
方法验证:
我们方别打印方法地址
NSLog(@"person1的setAge:方法地址 %p", [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person2的setAge:方法地址 %p", [self.person2 methodForSelector:@selector(setAge:)]);
可以观察到:person2
的setAge:
方法在GYPerson
中,而person1
的setAge:
方法却在foundation
中的_NSSetLongLongValueAndNotify
中。
2.对生成的子类NSKVONotifying_GYPerson
类中setAge:
方法猜测
3.验证_NSSetIntValueAndNotify
中的调用流程:
我们可以在GYPerson
中实现重写 willChangeValueForKey:
与 didChangeValueForKey:
方法,打印log,观察调用顺序。(didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法 )
结果
Tips
1.在NSKVONotifying_GYPerson
类中,重写了以下方法,从而让开发者不会产生过多疑惑。改新生成子类中包含一些其他方法,可以通过RunTime
的class_copyMethodList
及其他函数查看或验证。
// 内部实现,隐藏了NSKVONotifying_MJPerson类的存在 [self.person class]获取出的类名都是GYPerson
- (Class)class {
return [GYPerson class];
}
2.在工程中手动创建NSKVONotifying_GYPerson
类,会导致addObserver失败
3.手动触发KVO的方法,其实就是手动调用willChangeValueForKey
和didChangeValueForKey
方法,但是这两个方法需要成对出现,尽管触发observeValueForKeyPath
的操作是在didChangeValueForKey
中,可以理解为父类中添加了相关的判断。