Cocoa 深入理解KVO

  1. KVO的使用
  2. 原理
  3. 额外扩充

深入理解KVO
……

KVO的使用

要实现will/didChangeValueForKey:方法

被kvo的实例 实际上在运行时被调用

1
2
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

触发

1
2
3
4
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;

因此要实现KVO,需要下列条件之一即可

  1. 实现了KVC setValue:forKey:会调用 willChangeValueForKey,didChangeValueForKey
  2. 有访问器方法(KVO后,会运行时重写setter方法,添加willChangeValueForKey,didChangeValueForKey)
  3. 显示调用will/didChangeValueForKey:

原理

那系统是如何在框架层面支持 KVO的呢

核心

  1. isa的改变
  2. 动态创建子类
  3. 替换重写setter方法
  4. 重写class方法

动态创建被观察对象的子类,重写setter方法,并且要管理一个对象的所有观察者.

  1. 动态创建子类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 子类名
    NSString *kvoclassName = [kXJYKVOPrefix stringByAppendingString:originalclassName];
    Class class = NSClassFromString(kvoclassName);
    if (class) {
    return class;
    }
    // class doesn't exist yet, make it
    Class originalclass = object_getClass(self);
    Class kvoclass = objc_allocateClassPair(originalclass, kvoclassName.UTF8String, 0);
    // 获得签名
    Method classMethod = class_getInstanceMethod(originalclass, @selector(class));
    const char *types = method_getTypeEncoding(classMethod);
    // 替换setter 实现
    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    objc_registerClassPair(kvoclass);
    return kvoclass;
  2. 改变isa指针
    isa指针的作用: isa指针指向实例的类(对于这里的情况,isa指针的内容请看我的另一片博客)
    实例通过isa指针找到类,可以得到方法列表,属性列表等信息
    当我们将isa指针指向子类时,就可以调用子类的方法,使用子类的属性等。
    于是,调用该实例的setter方法其实是调用了子类的setter方法。

  3. 管理观察者
    由于一个对象可能被多个观察者观察,所以可以用关联对象的方法来管理所有的观察者。

    1
    2
    3
    4
    5
    6
    7
    8
    XJYObservationInfo *info = [[XJYObservationInfo alloc]initWithObserver:self key:key block:block];
    // 维护改KVO观察者数组
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kXJYKVOAssociatedObservers));
    if (!observers) {
    observers = [NSMutableArray array];
    objc_setAssociatedObject(self, (__bridge const void *)(kXJYKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
  4. 重写class方法

    1
    2
    3
    4
    5
    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    static Class kvo_class(id self, SEL _cmd)
    {
    return class_getSuperclass(object_getClass(self));
    }
  5. 子类 setter 的实现
    在这个派生类中重写基类中任何被观察属性的 setter 方法。
    派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。

额外扩充

还记得Aspects中 对于KVO的特殊处理吗,KVO改变了实例对象的isa指针,在此处 Aspects对KVO过的实例进行了特殊的处理
Aspects:

1
2
3
4
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}

1
2
3
4
object.class 由于KVO重写了class方法,所以不能准确的找到类
object_getClass()方法可以准确的找到isa指针
object.class 与 object_getClass(object)进行判断 来防止KVO导致的AOP无效
script>