KVOController源码全解析

前言

在阅读公司源码的一些功能时发现了KVOController这个神奇的库。这个库十分的好用,可以主动的去观察一个对象的属性。

例如

[self.KVOControllerNonRetaining observe:_test
                                    keyPath:@"test"
                                    options:0
                                      block::^(id _Nullable observer, id object, NSDictionary<NSString *, id> *change) {
                                          
                                      }];

KVOController的源码不多,加上分类也就不到800行,所以我花了一段时间阅读它的源码,这篇文章是阅读源码的总结。

类图

下图是我通过阅读源码画的UML类图,因为有些偷懒,所以这个类图的方法并不全。但这并不重要,这张类图的意义在于我们能够清晰地看明白他们之间的关系。

8216167-6d7422401bf1329a.png
KVOController类图.png

解析

_FBKVOInfo

_FBKVOInfo作为一个被两个类组合的类,在KVOController中属于Model的性质,用来保存所需要的内容,以下是这个类拥有的变量

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

_controller

_FBKVOInfo在FBKVOController中初始化,初始化时就把FBKVOController对象持有了,这里用一个weak修饰防止循环引用

_keyPath

这个应该不怎么需要解释,这个就是KVO观察的keyPath

_options

这个也是KVO观察的设置,是一个枚举,设置不同的枚举KVO效果是不同的,这里就不详细展开了。

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
    NSKeyValueObservingOptionNew = 0x01,
    NSKeyValueObservingOptionOld = 0x02,
    NSKeyValueObservingOptionInitial = 0x04,
    NSKeyValueObservingOptionPrior = 0x08
};

_action、_block

这个是用来保存FBKVOController需要调用的方法和block

context

上下文,这个也不多解释

_state

这是一个很重要的枚举,用来保存_FBKVOInfo所对应对象的观察状态

typedef NS_ENUM(uint8_t, _FBKVOInfoState) {
// 初始化状态
  _FBKVOInfoStateInitial = 0,
// 被观察状态
  _FBKVOInfoStateObserving,
// 没被观察状态
  _FBKVOInfoStateNotObserving,
};

FBKVOController

FBKVOController是KVOController对外暴露的类,其中我们主要用以下两个方法

- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;

- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action;

一个是KVO之后的block的回调,另一个是KVO之后调用的方法,下面我们以第一个方法进行讲解。

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

方法执行步骤:

  1. 断言以及错误判断
  2. 创建一个_FBKVOInfo对象
  3. 调用_observe:info:

根据上文的结论,我们可以得知_FBKVOInfo是一个Model的存在,所以需要先把它初始化了。

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

_objectInfosMap是临界资源,所以在这个方法里进行加锁防止资源被争抢。

方法执行步骤:

  1. 加锁
  2. 判断是否存在,存在即解锁结束,不需要再次观察;不存在则进入步骤3
  3. 判断_objectInfosMap所对应的集合是否存在,存在则继续;不存在则初始化并保存在_objectInfosMap中
  4. 保存新的_FBKVOInfo对象
  5. 解锁
  6. 调用_FBKVOSharedController

这里涉及到一个知识点是NSMapTable,这是一个类似NSDictionary的容器,但是它不仅能做到key和value之间的映射关系,它也能做到object和object之间的映射关系。这种object和object之间的映射关系在KVOController中体现的很好,每一个被观察者(object)对应一个_FBKVOInfo对象(object)。推荐阅读NSMapTable: 不只是一个能放weak指针的 NSDictionary

_FBKVOSharedController

_FBKVOSharedController它是一个单例,这个私有类才是KVOController提供服务的实际实现类。

我们继续来看_FBKVOSharedController被FBKVOController所调用的方法

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

方法执行步骤:

  1. 添加临界资源
  2. 注册观察
  3. 判断_FBKVOInfo对象state,若为初始化,则改变为观察中,若为不在观察中,则移除这个观察

这里涉及到NSHashTable,这个类似于NSSet,本文对此不展开说明。

之所以说_FBKVOSharedController才是KVOSharedController的实际实现类是因为它实现了KVO的回调方法

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary<NSString *, id> *)change
                       context:(nullable void *)context

我们来看一下里面的内容

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

  _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;
    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<NSString *, 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];
        }
      }
    }
  }
}

方法执行步骤

  1. 断言
  2. 通过context上下文从临界资源_infos中拿到info
  3. 进行保护,防止持有的FBKVOController和observe为空
  4. 判断_info持有的block或SEL是否存在,存在则调用;不存在则把消息转发给observe

最后一步调用发现block或者SEL都不存在时必须让object调用,因为observe里可能存在observeValueForKeyPath的实现

为什么使用FBKVOController不需要移除通知

在FBKVOController的dealloc里是这样写的

- (void)dealloc
{
  [self unobserveAll];
  pthread_mutex_destroy(&_lock);
}

unobserveAll所调用的是

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

可以发现FBKVOController通过遍历Map,把所持有的观察者都一一去除了

最终调用的方法是_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;
  }
}

这个方法可以看出来object所对应的_FBKVOSharedController所持有的_FBKVOInfo全部都被removeObserver了

"NSObject+FBKVOController.h"

KVOController还有一个NSObject的分类,提供两种方式使用KVOController的懒加载,分别是持有方式和不持有方式。

- (FBKVOController *)KVOController
{
  id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey);
  
  // lazily create the KVOController
  if (nil == controller) {
    controller = [FBKVOController controllerWithObserver:self];
    self.KVOController = controller;
  }
  
  return controller;
}

- (FBKVOController *)KVOControllerNonRetaining
{
  id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey);
  
  if (nil == controller) {
    controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO];
    self.KVOControllerNonRetaining = controller;
  }
  
  return controller;
}

他们的区别就是被观察者的内存管理机制是strong还是weak,前者是strong,后者是weak。

猜你喜欢

转载自blog.csdn.net/weixin_33858485/article/details/86987613