NSNotificationCenter 与 KVO 的实现比较

NSNotificationCenterKVO 的作用都是类与类之间的通信,但是实现方式却十分不同。


NSNotificationCenter

NSNotificationCenter 的实现原理相对简单,Mike Ash 简单实现了一个原理相类似的 NotificationCenter,感兴趣的可以阅读原文源码。当然,系统内部的实现要考虑更多因素,比如线程安全之类,要比这个复杂得多。

我们可以简化猜测认为 NSNotificationCenter 就是维护一个 NSMutableDictionarykey 是由 notificationNamesender 生成的 notification object,比如 NSNotificationvalue 是向 observer 发送的 selector,可以类比成 block 调用,由于一个 notification 可以广播给多个 observer,所以 value 应该是由多个 block 调用组成的容器,比如 NSSet。需要注意的是,由于 notificationNamesender 都可以为 nil,所以内部实现 postNotification 时,除了 post 原本的 notification,还需要 post 另外三种情况的 notification

  • notificationName == nil
  • sender == nil
  • notificationName == nil && sender == nil

Mike 的实现中该部分的处理如下:

- (void)postNotification: (NSNotification *)note
{
    NSString *name = [note name];
    id object = [note object];

    [self _postNotification: note name: name object: object];
    [self _postNotification: note name: name object: nil];
    [self _postNotification: note name: nil object: object];
    [self _postNotification: note name: nil object: nil];
}

其它例如 isEqualhash 方法重写等不涉及原理的方面不再赘述,原文和源码中均有详细阐述,仔细阅读会有不小的收获。


KVO

KVO 的实现主要是借助 Objective-C runtime 的力量。当你第一次观察一个对象时,会在 runtime 期间动态创建一个新的类,该类继承自原来对象的类(我们暂时称它为 KVO 子类,它的类名为 NSKVONotifying_OriginalClassOriginalClass 是原来对象的类名)。在 KVO 子类中重写了被观察属性的 -setter 方法,重写后的 -setter 方法会在调用原 -setter 方法之前和之后将观察值的变化通知观察者。最后将这个对象的 isa 指针指向 KVO 子类(isa 指针告诉 Runtime 系统这个对象的类是什么,如果对 Objective-C 对象模型不了解,可以参考唐巧的这篇博文),原对象就摇身一变成了该 KVO 子类的实例。另外,Apple 重写了 -dealloc 方法来处理一些额外的清理工作,添加了 -_isKVOA 私有方法供内部使用,为了不暴露 KVO 的内部机制,Apple 还重写了 KVO 子类的 -class 方法,返回原本那个父类。

  • 没有被观察的属性不会被重写 setter 方法。
  • object_setClass() 方法用来将 isa 指针指向 KVO 子类,用 object_getClass() 取代 -class 可获取对象真正的类。

需要注意的是,AppleFoundation 中实现了一系列 -setter 方法重写,所以我们只能对指定类型的变量运用 KVO

__NSSetBoolValueAndNotify  
__NSSetCharValueAndNotify  
__NSSetDoubleValueAndNotify  
__NSSetFloatValueAndNotify  
__NSSetIntValueAndNotify  
__NSSetLongLongValueAndNotify  
__NSSetLongValueAndNotify  
__NSSetObjectValueAndNotify  
__NSSetPointValueAndNotify  
__NSSetRangeValueAndNotify  
__NSSetRectValueAndNotify  
__NSSetShortValueAndNotify  
__NSSetSizeValueAndNotify  
__NSSetUnsignedCharValueAndNotify  
__NSSetUnsignedIntValueAndNotify  
__NSSetUnsignedLongLongValueAndNotify  
__NSSetUnsignedLongValueAndNotify  
__NSSetUnsignedShortValueAndNotify