FBKVOController原理探究

1,169 阅读9分钟

前言

FBKVOController是对系统的KVO的一个封装,通过合理利用对象的生命周期实现了自动销毁,并合理地保存观察者与被观察之间的关系

再探究他之前,需要回顾下KVO的一些基本知识,前面讲了添加监听方法如下所示:

- (void)addObserver:(NSObject *)observer
	forKeyPath:(NSString *)keyPath
    options:(NSKeyValueObservingOptions)options
    context:(nullable void *)context

该方法会对被观察者添加一个监听对象observer,通过该监听对象observer来观察其某一个键值keyPath,因此可以引出观察者、被观察者的关系

1.当被观察被某一个对象observer观察时,可以观察其多个键值,可以有多个回调

2.多个被观察者可以被同一个对象observer观察,因此每一个观察者可以监听多个被观察对象的多个属性

3.每个被观察者,可以添加多个observer对象(也就意味着被多个对象observer观察),因此当observer销毁时不能移除被观察者的所有监听,只能移除监听对象observer的监听

其关系如下图所示:

FBKVOController探究

FBKVOController中一共有三个核心类_FBKVOInfo、_FBKVOSharedController、FBKVOController

平时使用FBKVOController的方式如下,也就是给观察者所在的类持有KVOControler对象,因此当被观察对象释放时,KVOController对象也会随之释放并调用其dealloc回调,在里面可以移除与其相关的监听

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

// observe clock date property
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {

}];

下面通过介绍三个核心类来一步一步解开FBKVO的原理

_FBKVOInfo

如下面源码所示,_FBKVOInfo定义了下面几个参数:

_controller:观察者持有的KVOController用于检查该对象是否已经释放

_keyPath: 观察的键值

_options、_context:通过addObserver传递的枚举类型和_context

_action、_block:当触发KVO时的回调

_state:_FBKVOInfo所处的枚举状态

可以猜测到KVOInfo为一个监听对象的载体,可以参考最上面的图,其实际为观察者和被观察者行程的多对多关系的基本载体,即:里面有监听必须的回调方法和对应的keyPath

{
@public

  __weak FBKVOController *_controller;
  NSString *_keyPath;
  NSKeyValueObservingOptions _options;
  SEL _action;
  void *_context;
  FBKVONotificationBlock _block;
  _FBKVOInfoState _state;
}

并且其重写了hash和isEqual方法,因此在set里面,相同keyPath的KVOInfo会被认为相同(NSHashTables是可以使用指针来判断,结果可能不一样)

- (NSUInteger)hash
{
  return [_keyPath hash];
}

- (BOOL)isEqual:(id)object
{
  if (nil == object) {
    return NO;
  }
  if (self == object) {
    return YES;
  }
  if (![object isKindOfClass:[self class]]) {
    return NO;
  }
  return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}

FBKVOController与添加监听

FBKVOController就是被观察者持有的属性对象KVOController,为调用者独立出来的监听功能模块对象,也为我们使用的类的入口对象,我们可以通过其主动观察对象,就如其前面提到的使用案例一样,其会随着持有者(调用观察者方法的对象)的释放而释放

参考最上面的图,FBKVOController实际上只针对一个观察者与多个被观察者形成的一对多关系

FBKVOController的成员变量如下所示:

{
  //以被观察者对象为key,每一个value里面保存的是其对应的被观察者的键值回调基本载体(_FBKVOInfo)集合
  //观察者会观察多个对象(被观察者),因此会有多个key
  //_objectInfosMap里面保存着观察者所观察的所有对象(被观察者)的基本载体集合
  NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap; //对于NSMapTable可以参考前面的文章这里的使用和字典一样
  pthread_mutex_t _lock; //
}

系统为我们提供的释放方法,以观察回调为block的为例

观察者持有FBKVOController对象,以便于其跟随观察者的释放而释放
self.KVOController = = [FBKVOController controllerWithObserver:self];
//添加监听
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {
}];

就以上面的block回调方法为例(sel其实逻辑一样),我们看看其内部做了什么

- (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;
  }

  // 创建_FBKVOInfo,其保存这对应关系的keyPath和回调方法
  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];

  // 开启观察
  [self _observe:object info:info];
}

上面的方法调用完毕后调用了_observe:info方法,其仍然为FBKVOController的对象方法,其内部检查了被观察者基本载体是否已经存在了(即:被观察者对应的键值是否已经被该对象监听),代码如下

- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
  // lock 加锁同步锁,避免多线程读写时造成数据读写错误
  pthread_mutex_lock(&_lock);

  //_objectInfosMap保存在以对象为key的的set对象,set里面包含着对该被监听者对应的键值回调基本载体(_FBKVOInfo)的集合
  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // check for info existence
  //检查被观察对象所对应的键值回调基本载体是否已经存在(该键值是已经被该对象所观察)
  //由于重写hash等方法为对应keyPath,keyPath相同的在set中则认为相同
  _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]; //保存该基本载体到该对象对象的set中

  // unlock prior to callout
  pthread_mutex_unlock(&_lock);
	
  //通过_FBKVOSharedController调用系统的方法添加实际监听
  [[_FBKVOSharedController sharedController] observe:object info:info];
}

:通过上面也可以看到,新的 _FBKVOInfo 会覆盖掉老的,他们通过 keyPath作为判断条件,由于通过不同对象 object来控制一组 info 的 set,因此当在一个tableViewCell复用过程中,如果被观察者同一个 object的话(例如:controller),则只有最新一组能够正常响应,因此,可以使用model作为被观察者 object,来解决这个问题(如果是cell,则只有cell显示的能响应变化,根据情况而来即可)

介绍到这里这里引出了_FBKVOSharedController,我们先不查看怎么移除的,来看看_FBKVOSharedController他是干啥的

_FBKVOSharedController与添加监听

_FBKVOSharedController为一个单例对象,其通过NSHashMap对象保存这所有的基本载体(观察者与被观察者行程的多对多的基本载体集合),可以参考最上面的图

注意:其为所有FBKVOSharedController对象的代理,FBKVOSharedController通过该单例对象来调用系统观察方法来实现监听和移除监听

_FBKVOSharedController对象如下所示

{
  //保存所有基本载体的哈希表,注意其设置为NSPointerFunctionsWeakMemory属性,
  //当里面_FBKVOInfo单体对象被销毁时,将会被移除该hashTable
  NSHashTable<_FBKVOInfo *> *_infos; 
  pthread_mutex_t _mutex;
}
//单例对象
+ (instancetype)sharedController
{
  static _FBKVOSharedController *_controller = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _controller = [[_FBKVOSharedController alloc] init];
  });
  return _controller;
}

通过上面的FBKVOController的添加监听方法走到了_FBKVOSharedController的实际监听方法,其调用了系统的监听对象,也就意味着所有的观察实际上都是在这个类中添加和回调的

添加监听方法:

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

  // register info,将info添加到hashTable中,以便以回调使用
  pthread_mutex_lock(&_mutex);
  [_infos addObject:info];
  pthread_mutex_unlock(&_mutex);

  // add observer
  //调用系统方法添加监听,回调对象为_FBKVOSharedController单例对象
  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

  //调整info的状态,并且当info不在被观察是及时移除
  //特殊场景下,给定枚举值为NSKeyValueObservingOptionInitial, 并且在回调中取消注册则可能发生安全问题
  if (info->_state == _FBKVOInfoStateInitial) {
    info->_state = _FBKVOInfoStateObserving;
  } else if (info->_state == _FBKVOInfoStateNotObserving) {
    // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
    // and the observer is unregistered within the callback block.
    // at this time the object has been registered as an observer (in Foundation KVO),
    // so we can safely unobserve it.
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
}

系统的默认监听回调

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
                       context:(nullable void *)context
{
  NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);

  //检查对应info是否还存在,如果不存在结束
  _FBKVOInfo *info;

  {
    // lookup context in registered infos, taking out a strong reference only if it exists
    pthread_mutex_lock(&_mutex);
    info = [_infos member:(__bridge id)context];
    pthread_mutex_unlock(&_mutex);
  }

  if (nil != info) {

    // take strong reference to controller
    FBKVOController *controller = info->_controller;
    //查看KVOController即调用者持有的观察对象是否存在,不存在则意味着已经销毁结束
    if (nil != controller) {

      // take strong reference to observer
      id observer = controller.observer;
      //观察者还存在的的情况回调block和action
      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];
        }
      }
    }
  }
}

移除监听

前面已经介绍到,FBKVOController会随着调用者的销毁而销毁,因此当其销毁时,会调用其dealloc方法,需要主动在里面移除该对象对被观察的所有监听

FBKVOController的dealloc方法实际调用了_unobserveAll方法来移除所有_FBKVOInfo信息

注意:当_FBKVOInfo被销毁时,_FBKVOController里面对应的_FBKVOInfo也会被移除

由于_FBKVOController实际上为FBKVOController的代理监听对象,因此添加和监听的移除也会在这个对象里面进行

- (void)_unobserveAll
{
  // lock
  pthread_mutex_lock(&_lock);
  
  //由于移除后里面的对象将会被销毁,所以这里系统copy了一份,为了移除监听做准备
  NSMapTable *objectInfoMaps = [_objectInfosMap copy];

  // clear table and map
  //移除_objectInfosMap所有的基本载体,与此同时_FBKVOSharedController中对应的载体也被移除
  [_objectInfosMap removeAllObjects];

  // unlock
  pthread_mutex_unlock(&_lock);
	
  _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
 
  //在_FBKVOSharedController里面,根据监听对象移除所有的监听
  for (id object in objectInfoMaps) {
    // unobserve each registered object and infos
    NSSet *infos = [objectInfoMaps objectForKey:object];
    [shareController unobserve:object infos:infos];
  }
}

_FBKVOSharedController里面的移除监听,为了避免其他问题,其主动移除所有的基本载体,并且将载体的观察状态调整,避免多线程产生的安全问题,实现方法如下所示

- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos
{
  if (0 == infos.count) {
    return;
  }

  // unregister info
  //移除所有基本载体,避免回调问题
  pthread_mutex_lock(&_mutex);
  for (_FBKVOInfo *info in infos) {
    [_infos removeObject:info];
  }
  pthread_mutex_unlock(&_mutex);

  // remove observer
  //调整监听状态,避免多线程产生的安全问题
  for (_FBKVOInfo *info in infos) {
    if (info->_state == _FBKVOInfoStateObserving) {
      [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
    }
    info->_state = _FBKVOInfoStateNotObserving;
  }
}

到这里其就已经介绍的差不多了,其他的方法基本上都是依照该方法编写的,这里就不多介绍了

总结

1.当使用某个类添加某个功能是,并且在该类销毁时需要主动移除这个功能带来的副作用是,可以把功能独立出来,作为属性,充分利用生命周期来解决问题

2.NSHashTable和NSMapTable的合理使用,能避免许多问题

3.充分理解功能模块中产生的逻辑关系链(观察者与被观察者多对多的关系,一个观察者与多个被观察者一对多的关系),可能封装出使用更广泛,更加优秀的API

4.这里锁的使用是为了保证公共数据读写的安全问题(避免读取出错误信息),不必要的功能不必加锁