KVC与KVO
一、KVO
KVO全称Key-Value-Observing,作用是对象监听另一个对象特定的属性的改变,并在改变时接收到事件,举例如下:
如果person对象的name属性改变了,就会调用监听者BaseController的observeValueForKeyPath方法。
1.1 KVO基本使用
1)通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件。
2)在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
3)当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。
1.2 KVO实现原理
1)为原生的类生成一个名为NSKVONotifying_***的子类,子类重写了属性的set方法,里面调用了willChangeValueForKey和didChangeValueForKey,还重写了class方法,让其看着更像原类,然后obj的isa指向了监听类。set方法大致就如下图所示:
2)在调用obj的属性set方法时,在didChangeValueForKey:方法中自动调用了所有Observe的observeValueForKeyPath:ofObject:change:context: 方法。这样就实现了属性监听。
1.2.1 KVO中间类验证:
在添加监听的addObserver:forKeyPath:options:context:方法下面得到obj的Class类和父类,代码如下:
打印效果如下:
1.2.2 验证 didChangeValueForKey: 内部调用监听方法
在Person类中,重写重写 willChangeValueForKey: 和 didChangeValueForKey: 方法,模拟它们的实现。具体代码如下:
打印效果如下:
二、bk中的KVO
BlockKit第三方库中有个NSObject_BKBlockObservation.m的文件,提供了很方便加入Observe功能的一些API,比如- (NSString *)bk_addObserverForKeyPath:(NSString *)keyPath task:(void (^)(id target))task方法,主要解决传统KVO的以下几个问题:
1)KVO使用不当很容易导致崩溃,比如重复add和remove、Observer被释放、keyPath传错等。
2)写起来太麻烦,每个Observe都得重写observeValueForKeyPath:ofObject:change:context:
2.1 bk的KVO的原理
在添加Observer时,最终都会调入接口
- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths
identifier:(NSString *)identifier
options:(NSKeyValueObservingOptions)options
context:(BKObserverContext)context
task:(id)task
传参说明如下:
参数名称(类型) | 备注 |
keypaths(NSArray *) | 所有被监听的属性 |
identifier(NSString *) | 这次添加监听的唯一ID |
options(NSKeyValueObservingOptions) | 监听task中需要回传的参数 |
context(BKObserverContext) | task需要回传什么 |
task(id) | 具体的block |
具体代码如下:
2.2 _BKObserver对象
_BKObserver其实就是一个监听对象,是task和被监听对象的中介者。实现了
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
来具体调用task。
2.2.1 _BKObserver的构造方法:
2.2.2 _BKObserver开始监听startObservingWithOptions
startObservingWithOptions是开启监听的入口,对被监听对象添加所有keyPath的监听,具体代码如下:
2.2.3 _BKObserver中的监听方法
_BKObserver类的监听方法,当keyPath发生变化时,调用task回调,具体代码如下:
2.3 dealloc中的删除所有监听
入口函数是bk_removeAllBlockObservers,具体代码如下:
stopObserving函数又调用了_stopObservingLocked,作用就是将被监听对象添加的所有keyPath的监听全部remove。具体代码如下:
三、KVC
KVC全称是Key Value Coding,KVC是一种利用字符串来访问对象的属性方法或者成员变量机制。原理主要依赖与setter和getter方法。另外accessInstanceVariablesDirectly表示在搜索的过程中是否允许读取实例变量的值,默认为YES。
3.1 赋值原理
以setValue:forKey:为例,运行步骤如下:
1)以set<Key>、_set<Key>的顺序查找对应命名的setter方法,如果找到的话,调用这个方法并将值传进去。
2)如果没有找到setter方法,但是accessInstanceVariablesDirectly类方法返回YES,则按_<Key>、_is<Key>、<Key>、is<Key>的顺序查找一个对应的实例变量,如果找到那么将value赋值。
3)如果没有找到setter方法或实例变量,则调用setValue:forUndefinedKey:方法,默认是抛出异常,也可进行重写。
流程图如下:
3.2 取值原理
以valueForKey:为例,运行步骤如下:
1)以get<Key>、<Key>、is<Key>、_<Key>的顺序搜索方法,如果有,直接调用。
2)如果没有getter方法,但是accessInstanceVariablesDirectly类方法返回YES,那么按照_<Key>、_is<Key>、<Key>、is<Key>的顺序查找实例变量。
3)如果返回值是一个对象指针,则直接返回。如果是一个基础数据类型,而且Number类型支持,那么被Number封装再返回,如果不被支持,那么NSValue封装并返回。
4)在上述情况都失败的情况下调用valueForUndefinedKey:方法,默认是抛出异常,也可进行重写。
流程图如下: