iOS开发中,有一种设计模式应用广泛,那就是观察者模式。苹果称其为 KVO(Key-Value Observing),既键值观察,总是有人把 KVC(Key-value coding) 和 KVO 混为一谈,实则它们只是名字长得像。本文将分别介绍KVO的实现原理以及KVC的主要用法。
KVO
KVO的使用
使用KVO分为三个步骤:
- 通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件。
- 在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
- 当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。
- (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<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
KVO的原理
注册观察者时,系统做了什么
当首次通过addObserver:forKeyPath:options:context:方法注册观察A时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。
此时,系统会:
- 检查对象的类有没有相应的 setter 方法。如果没有抛出异常;
- 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;
- 检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;
- 添加这个观察者
为什么更改setter就可以实现KVO的监听呢
NSKVONotifying_A重写了原来类的setter方法,具体实现是下面几行代码
- (void)setValue:(id)obj
{
[self willChangeValueForKey:@"keyPath"];
//调用父类实现,也即原类的实现
[super setValue:obj];
[self didChangeValueForKey:@"keyPath"];
- 首先调用willChangeValueForKey方法
- 然后调用父类实现,也就是原来类的setter实现
- 然后调用didChangeValueForKey,这个方法会触发observeValueForKeyPath这个KVO回调,来通知观察者value发生了变化
KVC
KVC是一种键值编码机制(key-value),通过NSKeyValueCoding协议来间接访问成员变量
它会破坏面向对象编程思想,上面的key是没有任何限制的,当我们知道一个类内部的某个私有成员变量名称,就可以通过key进行设置和访问
KVC的使用
可以随意修改一个对象的属性或者成员变量,因为key没有限制,私有也可以
赋值: 设置某一对象当中和key同名或者相似名字的实例变量的值
- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)key; 通过点可层层赋值
取值: 获取和key同名或者相似名字的实例变量的值
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)key;
KVC实现
KVC系统内部实现流程:
赋值过程
先找相关方法set< Key > _set< Key > setIs< Key >
若没有相关方法,判断是否可以直接方法成员变量accessInstanceVariablesDirectly == YES ?
YES:继续找相关变量 _< Key > _is< Key> < Key> is< Key>赋值,若都不存在,则调forUndefinedKey,抛出异常
NO:执行setValue: forUndefinedKey 系统抛出异常:未定义的key
取值过程
先找相关方法get< Key> < Key> < countOfKey> && objectInKeyAtIndex
若没有相关方法,判断是否可以直接方法成员变量accessInstanceVariablesDirectly == YES ?
YES: 继续找相关变量 _< Key> _is< Key> < Key> is< Key>赋值,若都不存在,则调forUndefinedKey,抛出异常
NO: 执行setValue: forUndefinedKey 系统抛出异常:未定义的key
其他
赋值为空: setNilValueForKey
key值不存在: setValue: forUndefinedKey
validateValue方法的工作原理: 属性值正确性验证,用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因
kvc什么情况下会崩溃?如何防护?
崩溃原因:
- key 不是对象的属性,造成崩溃。
- keyPath 不正确,造成崩溃。
- key 为 nil,造成崩溃。
- value 为 nil,为非对象设值,造成崩溃。
如何解决:
- 如果属性存在,利用iOS的反射机制来规避,NSStringFromSelector(@selector())将SEL反射为字符串作为key。这样在@selector()中传入方法名的过程中,编译器会有合法性检查,如果方法不存在或未实现会报黄色警告。
- 重写类的setValue:forUndefinedKey: ,valueForUndefinedKey: 和setNilValueForKey: .
参考文章:
透彻理解 KVO 观察者模式
如何自己动手实现 KVO
www.mikeash.com/pyblog/frid…