SerendipityEx

关注成长,记录生活

KVO & KVOController

KVO的实现机制

KVO 是 Objective-C 对观察者模式的实现。从苹果的 文档 中对于它的实现描述可以知道: KVO 是通过 isa-swizzling 实现的。 当你观察一个对象时,该对象的isa指针被修改,指向一个中间类,而不是其真实的类。但是具体的实现苹果的文档里并没有详细说明。

事实上,这个中间类会在对象被观察时动态地被创建。继承自该对象原来的类,并且重写被观察属性的 setter 方法。重写的方法负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。然后将 isa 指针指向新创建的类。这样对象就变成了新创建的子类的实例了。

参考 Mike Ash 的 Friday Q&A 2009-01-23

测试代码地址

KVO 的缺点

KVO的缺点在于它的API,我们只能通过 observeValueForKeyPath: ofObject: change: context: 来获得通知。无法通过自定义 selector 或 block 来处理通知事件。因此我们可能需要一些判断来处理不同的情况。

在了解了kov的实现机制后,我们可以使用block实现KVO.

参考:使用Block实现KVO如何自己动手实现 KVO

KVOController

KVOController是Facebook 推出的一个开源库。它是基于 Cocoa 的KVO 的一次封装,提供简单的API,并且是线程安全的。

使用方法:

// create KVO controller with observer
FBKVOController *KVOController = [FBKVOController controllerWithObserver:self];
self.KVOController = KVOController;

// observe clock date property
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {
  // update clock view with new value
  clockView.date = change[NSKeyValueChangeNewKey];
}];

在使用 KVOController时,我们不需要关注在何时移除观察者,因为KVOController会在dealloc时自动将其移除。 使用 block 来处理KVO的通知事件,更是一种美好的体验。

项目结构

KVOController 整个项目结构只能有5个文件:

├── KVOController.h
├── FBKVOController.h
├── FBKVOController.m
├── NSObject+FBKVOController.h
└── NSObject+FBKVOController.m

其中,KVOController.h 是头文件,NSObject+FBKVOController 通过 AssociateObject 为 NSObject 提供一个 retain 和 非retain 类型的KVOController。 FBKVOController文件就是主要的技术实现。

在 FBKVOController 文件中主要定义了三个类:

  • _FBKVOInfo
  • _FBKVOSharedController
  • _KVOController

FBKVOController

成员变量以及初始化方法:

/**
 The observer notified on key-value change. Specified on initialization.
 */
@property (nullable, nonatomic, weak, readonly) id observer;
@implementation FBKVOController
{
  NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
  pthread_mutex_t _lock;
}

- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
  self = [super init];
  if (nil != self) {
    _observer = observer;
    NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
    pthread_mutex_init(&_lock, NULL);
  }
  return self;
}
  1. KVOController 维护了一个对观察者的弱引用,在初始化时,传入的observer赋值给_observer进行weak持有。
  • NSMapTable, 和NSDictionary 类似,但是是一个object-object的映射关系,并且可以指定 key、value的strong,weak 或者 copy 类型。通过retainObserved参数来生成对应的weak或者strong的NSPointerFunctionsOptions
  • _lock 互斥锁,保证线程安全。

FBKVOController对观察对象 add Observer

- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
  NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
  if (nil == object || 0 == keyPath.length || NULL == block) {
    return;
  }

  // create info
  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];

  // observe object with info
  [self _observe:object info:info];
}

- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
  // lock
  pthread_mutex_lock(&_lock);

  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // check for info existence
  _FBKVOInfo *existingInfo = [infos member:info];
  if (nil != existingInfo) {
    // observation info already exists; do not observe it again

    // unlock and return
    pthread_mutex_unlock(&_lock);
    return;
  }

  // lazilly create set of infos
  if (nil == infos) {
    infos = [NSMutableSet set];
    [_objectInfosMap setObject:infos forKey:object];
  }

  // add info and oberve
  [infos addObject:info];

  // unlock prior to callout
  pthread_mutex_unlock(&_lock);

  [[_FBKVOSharedController sharedController] observe:object info:info];
}

代码中对于实现思路做了很好的注释:

  1. 首先对于传入的操作进行断言判断。
  • 使用传入参数创建 _FBKVOInfo 实例 info。对info 添加观察[self _observe:object info:info];。
  • 对_objectInfosMap 进行检查,避免重复添加。并在读写时使用加锁,保证线程安全。
  • 添加成功后调用 [[_FBKVOSharedController sharedController] observe:object info:info]

实现隐式地移除KVO:

- (void)dealloc
{
  [self unobserveAll];
  pthread_mutex_destroy(&_lock);
}
- (void)_unobserveAll
{
  // lock
  pthread_mutex_lock(&_lock);

  NSMapTable *objectInfoMaps = [_objectInfosMap copy];

  // clear table and map
  [_objectInfosMap removeAllObjects];

  // unlock
  pthread_mutex_unlock(&_lock);

  _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];

  for (id object in objectInfoMaps) {
    // unobserve each registered object and infos
    NSSet *infos = [objectInfoMaps objectForKey:object];
    [shareController unobserve:object infos:infos];
  }
}

在 KVOController 释放的时候,从_FBKVOSharedController单例中移除 observer

_FBKVOSharedController

_FBKVOSharedController 被设计成单例,统一管理KVO通知的接收和转发。

@implementation _FBKVOSharedController
{
  NSHashTable<_FBKVOInfo *> *_infos;
  pthread_mutex_t _mutex;
}
  • _infos: NSHashTable类型,类似NSSet,同样也可以设置对象的强弱引用。
  • _mutex: 互斥锁,为了线程安全。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

  // register info
  pthread_mutex_lock(&_mutex);
  [_infos addObject:info];
  pthread_mutex_unlock(&_mutex);

  // add observer
  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

  if (info->_state == _FBKVOInfoStateInitial) {
    info->_state = _FBKVOInfoStateObserving;
  } else if (info->_state == _FBKVOInfoStateNotObserving) {
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
}
  • 将 info 加入 _infos.
  • [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info] 为object 添加观察者。

在 _FBKVOSharedController 实现observeValueForKeyPath:ofObject:Change:context 接收处理通知。

info = [_infos member:(__bridge id)context]; 在 _infos 中找到当前 _FBKVOInfo,并对它进行一个强引用。对数据进行封装,执行注册的回调。block, selector, 默认方法。

if (nil != info) {
    // take strong reference to controller
    FBKVOController *controller = info->_controller;
    if (nil != controller) {

      // take strong reference to observer
      id observer = controller.observer;
      if (nil != observer) {

        // dispatch custom block or action, fall back to default action
        if (info->_block) {
          NSDictionary<NSKeyValueChangeKey, id> *changeWithKeyPath = change;
          // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
          if (keyPath) {
            NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
            [mChange addEntriesFromDictionary:change];
            changeWithKeyPath = [mChange copy];
          }
          info->_block(observer, object, changeWithKeyPath);
        } else if (info->_action) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
          [observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
        } else {
          [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
        }
      }
    }
  }

unobserve ,在KVOController dealloc() 时会将 info 从 _infos中移除,并用移除观察者 ,从而实现隐式地移除KVO。

- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

  // unregister info
  pthread_mutex_lock(&_mutex);
  [_infos removeObject:info];
  pthread_mutex_unlock(&_mutex);

  // remove observer
  if (info->_state == _FBKVOInfoStateObserving) {
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
  info->_state = _FBKVOInfoStateNotObserving;
}

_FBKVOInfo

_FBKVOInfo 将 将观察者信息保存在一起,并且重写了 hash 和 isEqual,以 _keyPath作为唯一性判断依据。

@implementation _FBKVOInfo
{
@public
  __weak FBKVOController *_controller;
  NSString *_keyPath;
  NSKeyValueObservingOptions _options;
  SEL _action;
  void *_context;
  FBKVONotificationBlock _block;
  _FBKVOInfoState _state;
}

Reference

KVOController 如何优雅地使用KVO

[EOF]

Carthage的简单使用