OC基础之KVO和KVC

1,355 阅读4分钟

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…