iOS之KVO实现原理

当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写被观察属性keyPath的setter方法。setter方法负责通知观察对象该属性的改变状况。

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

  1. NSKVONotifying_A类

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

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

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

  2. 子类setter方法

    KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:didChangevlueForKey:,在存取数值的前后分别调用2个方法:

    被观察属性发生改变之前willChangeValueForKey:被调用,通知系统该keyPath的属性值即将变更;

    改变发生后didChangeValueForKey:被调用,通知系统该keyPath的属性值已经变更,触发observeValueForKey:ofObject:change:context:

    重写观察属性的setter 方法这种继承方式的注入是在运行时而不是编译时实现的。

    KVO为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

    1
    2
    3
    4
    5
    -(void)setName:(NSString *)newName{
    [self willChangeValueForKey:@"name"];//KVO在调用存取方法之前总调用
    [super setValue:newName forKey:@"name"];//调用父类的存取方法  
    [self didChangeValueForKey:@"name"];//KVO在调用存取方法之后总调用
    }
  3. 注意

    观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行KVO的回调方法,例如是否执行了setter方法、或者是否使用了KVC赋值。如果赋值没有通过setter方法或者KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这时是不会触发kvo机制,更加不会调用回调方法的。

    所以使用KVO机制的前提是遵循 KVO 的属性设置方式来变更属性值。