1、KVO
<Key-Value-Observing>
1、KVO
顾名思义,键值监听,可以用于监听某个对象属性值的变化。
KVO
是一个非正式协议,提供了一个途径,使对象(观察者)能够观察其他对象(被观察者)的属性,当被观察者的属性发生变化时,观察者就会被告知该变化。首先了解一下
KVO
的基本使用,然后在此基础上,我们深入了解一下
KVO
的底层实现原理。
//给一个对象属性添加KVO监听
[self addObserver:(nonnull NSObject *)
forKeyPath:(nonnull NSString *)
options:(NSKeyValueObservingOptions)
context:(nullable void *)]
//当监听对象的属性值发生改变时,就会调用
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
//需要在不使用的时候,移除监听
- (void)dealloc{
[self removeObserver:self forKeyPath:@""];
}
2、
KVO的底层实现原理
2、
在了解
KVO
底层之前,我们先要对
isa
一些基本的概念有个了解,
instance对象的isa指向class对象,class对象的isa指向Meta-Class对象,Meta-Class对象的isa指向基类的Meta-Class对象
。如果不了解的可以去看一下我的另一篇文章
《通俗易懂的ios方法调用底层原理》
。
KVO 是基于Runtime机制实现的,KVO是运用了一个
isa-swizzling
技术,就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法.
当某个类的属性对象
第一次被观察
时,系统就会在运行期
动态
地创建
该类的一个派生类
,在这个派生类中重写基类中被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的
通知机制
如果原类为Person,那么生成的派生类名为
NSKVONotifying_Person
每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
调用非监听属性设置方法,会通过
NSKVONotify_Person
的
superclass
,找到
Person
类对象,再调用其 实例方法键值观察通知依赖于NSObject 的两个方法:
willChangeValueForKey:
和
didChangevlueForKey:
在一个被观察属性发生改变之前,
willChangeValueForKey:
一定会被调用,这就 会记录旧的值。而当改变发生后,
didChangeValueForKey:
会被调用,继而
observeValueForKey:ofObject:change:context:
也会被调用。补充:KV0的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类。如果没有重写
class
方法,当该对象调用
class
方法时,会在自己的方法缓存列表,方法列表,父类缓存,方法列表一直向上去查找该方法,因为
class
方法是
NSObject
中的方法,如果不重写最终可能会返回
NSKVONotifying_Person
,就会将该类暴露出来,也给开发者造成困扰,写的是
Person
,添加KVO之后
class
方法返回怎么是另一个类。
3、kvo
和 notification(通知)的区别?
3、kvo
KVO
和
NSNotificationCenter
都是
iOS
中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,
KVO
是一对一的,而不是一对多的。
KVO
对被监听对象无侵入性,不需要修改其内部代码即可实现监听。notification 的优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,而且可以一对多。
4、
测试代码
4、
NSKeyValueObservingOptions option = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
Person *person1 = [Person new];
NSLog(@"person1添加KVO监听对象之前-类对象 -%@", object_getClass(person1));
NSLog(@"person1添加KVO监听之前-方法实现 -%p", [person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person1添加KVO监听之前-元类对象 -%@", object_getClass(object_getClass(person1)));
[person1 addObserver:self forKeyPath:@"age" options:option context:@"age chage"];
NSLog(@"person1添加KVO监听对象之后-类对象 -%@", object_getClass(person1));
NSLog(@"person1添加KVO监听之后-方法实现 -%p", [person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person1添加KVO监听之后-元类对象 -%@", object_getClass(object_getClass(person1)));
//打印结果
KVO-test[1214:513029] person1添加KVO监听对象之前-类对象 -Person
KVO-test[1214:513029] person1添加KVO监听之前-方法实现 -0x100411470
KVO-test[1214:513029] person1添加KVO监听之前-元类对象 -Person
KVO-test[1214:513029] person1添加KVO监听对象之后-类对象 -NSKVONotifying_Person
KVO-test[1214:513029] person1添加KVO监听之后-方法实现 -0x10076c844
KVO-test[1214:513029] person1添加KVO监听之后-元类对象 -NSKVONotifying_Person