美文网首页IOS
KVOController源码全解析

KVOController源码全解析

作者: QYiZHong | 来源:发表于2019-01-25 21:30 被阅读7次

    前言

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

    例如

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

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

    类图

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

    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。

    相关文章

      网友评论

        本文标题:KVOController源码全解析

        本文链接:https://www.haomeiwen.com/subject/kwwmjqtx.html