KVO原理

作者: SPIREJ | 来源:发表于2019-05-06 14:46 被阅读9次

从KVC说起

要说KVO害的先说下KVCKVC(Key-value coding)是一种基于NSKeyValueCoding非正式协议的机制,能让我们直接使用一个或一串字符串标识符去访问,操作类的属性。

常用的方法比如:

- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

通过这些方法加上正确的标识符(一般和属性同名),可以直接获取或者设置一个类的属性,甚至可以轻易越过多个类的层级结构,直接获取目标属性。

注册KVO

添加一个观察者的方法是-addObserver:forKeyPath:options:context:

- (void)addObserver:(NSObject *)observer
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context
  • observer:注册KVO通知的对象。观察者必须实现key-value observing方法observeValueForKeyPath:ofObject:change:context:
  • keyPath:观察者的属性的keyPath,相对于接受者,值不能为nil
  • options:``NSKeyValueObservingOptions的组合,它指定了观察通知中包含了什么,可以查看“NSKeyValueObservingOptions”
  • context:observeValueForKeyPath:ofObject:change:context:传给observer参数的随机数据

KVO基本原理:

  1. KVO是基于runtime机制实现的

  2. 当某个类的属性对象第一次被观察时,系统就会在运行期动态的创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。

  3. 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

  4. 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法

  5. 键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:didChangeValueForKey:,在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context:也会被调用。

KVO深入原理:

  1. Apple使用了isa混写(isa-swizzling)来实现KVO。当观察对象为类A的属性时,KVO机制动态创建一个新的名为:NSKVONotifying_A的新类,该类继承自A的本类,且KVONSKVONotifying_A重写观察属性的setter方法,setter方法会负责在调用原setter方法之前和之后,通知所有观察对象属性值的更改情况。

  2. NSKVONotifying_A类剖析:在这个过程,被观察对象的isa指针从指向原来的A类,被KVO机制修改为指向系统新创建的子类NSKVONotifying_A类,来实现当前类属性值改变的监听。

  3. 所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对KVO的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类,就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。

  4. isa指针的作用:每个对象都有isa指针,指向该对象的类,它告诉runtime系统这个对象的类时什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。因而在该对象上对setter的调用就会调用已重写的setter,从而激活键值通知机制。

  5. 子类setter方法剖析:KVO的键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:didChangeValueForKey:,在存取数值的前后分别调用2个方法:被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该keyPath的属性值即将变更;在改变发生之后,didChangeValueForKey:被调用,通知系统该keyPath的属性值已经变更;之后,observeValueForKey:ofObject:change:context:也会被调用。且重写观察属性的setter方法这种继承方式的注入是在运行时而不是编译时实现的。

image

附加

1、什么是KVC,说说它的优缺点?
答:KVC是一种不需要调用存取方法,就能直接通过实例变量访问对象属性的机制。很多情况下会简化程序代码。
但由于KVC不会对键和建路径进行错误检查,所以编译器无法检测错误。而且使用KVC后的执行效率要低于合成存取器,因为使用KVC必须先解析字符串,然后再设置或服务对象的实例变量。

2、NSNotification和KVO的区别和用法是什么?什么时候用通知,什么时候应该使用KVO,它们的实现上有什么区别吗?
答:NSNotification是通知模式在iOS的实现,KVO的全称是简直观察(Key-value observing),其实基于KVC(Key-value coding)的,KVC是一个通过属性名访问属性变量的机制。将Model层的变化,通知到多个Controller对象时,可以使用NSNotification;如果是只需要观察某个对象的某个属性,可以使用KVO

3、如何关闭默认的KVO的默认实现,KVO的实现原理?
答:所谓的“手动触发”是区别于“自动触发”:
自动触发是指类似这种场景:在注册KVO之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。
想知道如何手动触发,必须知道自动触发KVO的原理:
键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:didChangeValueForKey:。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context:也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。

参考

http://nshipster.cn/key-value-observing

相关文章

网友评论

    本文标题:KVO原理

    本文链接:https://www.haomeiwen.com/subject/oorroqtx.html