前言

一、FBKVOController的基本使用

二、整体源码结构

三、_FBKVOInfo

四、FBKVOController的构造方法和成员变量

五. 添加被观察者

六. 自动移除监听

七、简易流程图


### 经过对KVO底层的探索,基本掌握了KVO的实现原理。但是在尝试自定义KVO的过程中,还是遇到了不少问题,比如:“在动态子类中重写Setter方法的时候,需要判断所有的参数类型”以及“需要处理多线程的情况。”还需要实现手动监听功能等等。所以要想自定义一个完整的KVO,并没有那么简单。这里可以参考gnustep开源的代码: [点击下载](http://www.gnustep.org/resources/downloads.php)。换一种思路,如果不去自定义KVO,而是去封装系统的KVO,从而更加方便的使用KVO呢?那么本文就分析Facebook的一个框架:FBKVOController,看它是如何“优化KVO的使用”的。

一、FBKVOController的基本使用

    #cocoapods 依赖
    pod 'KVOController', '~> 1.2.0'

源码地址


1、相对系统KVO的优势

(1)支持block和action的回调方式。
(2)不用手动去移除观察者

2、角色转换

系统的KVO是被观察者去添加观察者。KVOController可以主动的去添加被观察者。

3、简单的例子

@property (nonatomic, strong) FBKVOController *fbKvo;
.....
self.fbKvo = [[FBKVOController alloc] initWithObserver:self]; 
[self.fbKvo observe:self.viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
                
 }];

以上就是只用FBKVOController的一个例子,监听了viewModel中的name属性。

4、同时支持Action的形式

 [self.KVOController observe:self.viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew action:@selector(observeChange:object:)];
.....
 - (void)observeChange:(NSDictionary *)dic object:(id)object {

}

当然KVOController 也支持系统原本的回调。

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

优先级为: block > action > 系统回调

5、通过分类直接获取KVOController对象.

//以上代码我们可以改为:
 [self.KVOController observe:self.viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
    
    }];

直接通过分类的关联属性获取KVOController。

@interface NSObject (FBKVOController)

@property (nonatomic, strong) FBKVOController *KVOController;

@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;

@end

这里KVOController会强持有被观察者。KVOControllerNonRetaining会弱引用被观察者,比如被观察者是观察者本身的时候,需要使用KVOControllerNonRetaining来解除循环引用。

6、不需要手动移除监听

内部会自动移除监听。


二、整体源码结构


其中FBKVOController.m中包含3个类
(1)FBKVOController
(2)_FBKVOInfo
(3)_FBKVOSharedController

三、_FBKVOInfo

先看_FBKVOInfo类:

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

有7个成员变量:_controller,_keyPath,_options,_action,_context,_block,_state。
其中_keyPath(监听的属性名称)、_options(策略)、_action(事件)、_block(回调) 这四个成员变量很好理解,action和block是在keyPath对应的值改变的时候触发响应回调的,_options和系统KVO中的一样,是监听值的策略。
_state就是记录监听状态,是一种容错机制。
而对于_controller、_context 这3个成员变量。有2个问题:
(1)_FBKVOInfo保存这2个成员的目的是什么呢?
(2)为什么_controller是弱引用?
先插个眼,继续往下分析。

四、FBKVOController的构造方法和成员变量

1、构造方法

@interface FBKVOController : NSObject

+ (instancetype)controllerWithObserver:(nullable id)observer;

- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithObserver:(nullable id)observer;

- (instancetype)init NS_UNAVAILABLE;

+ (instancetype)new NS_UNAVAILABLE;
..........................................
@end

@implementation FBKVOController
{
  NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
  pthread_mutex_t _lock;
}

构造方法中有两个宏定义:

NS_UNAVAILABLE 和 NS_DESIGNATED_INITIALIZER

NS_UNAVAILABLE 是告诉编译器此方法不可用,代码中如果调用此方法,会编译报错。(通过runtime依然可以调用)
NS_DESIGNATED_INITIALIZER 是告诉编译器,指定默认的初始化方法。
通过这两个宏,告诉我们,observer是必须在初始化FBKVOController时传进来的。

/**
 The observer notified on key-value change. Specified on initialization.
 */
@property (nullable, nonatomic, weak, readonly) id observer;

此observer是弱引用,原因如下面代码:

 self.KVOController = [[FBKVOController alloc] initWithObserver:self];

(->表示持有关系) self -> KVOController -> self。如果observer不是弱引用,就会循环引用。

2、成员变量:

@implementation FBKVOController
{
  NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
  pthread_mutex_t _lock;
}

_objectInfosMap是存储被观察对象和KVOInfo的映射关系。也就是一个被观察对象对应多个_FBKVOInfo。
_objectInfosMap是NSMapTable类型。

NSMapTable与NSDictionary/NSMutableDictionary 简单做一个对比:

NSDictionary/NSMutableDictionary 中对于key和value的内存管理方法唯一,即对key进行copy,对value进行强引用,而NSMapTable有key和value的内存管理策略。

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

通过初始化方法,可以看到,根据retainObserved来判断对key是使用weak还是strong。
另外一个成员变量_lock,是一个互斥锁。

五. 添加被观察者

1、调用方法

//这里以其中一个方法为例
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
    ..................
  // create info
  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
  // observe object with info
  [self _observe:object info:info];
}

创建一个_FBKVOInfo对象,然后执行 [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];
}

3、主要流程

(1)为了防止多线程可能造成数据混乱的问题,加了互斥锁。

(2)从_objectInfosMap中取出object对应的infos,判断info在infos中是否存在。
“这里可以确定_objectInfosMap的key是被观察者,value是info的集合。
也就是一个被观察者对应多个info。”

了解了_objectInfosMap的结构之后,总结回答2个问题:

<1>上面有提到“被观察者是观察者本身的时候,需要使用KVOControllerNonRetaining来解除循环引用”。

比如:

[self.KVOController observe:self keyPath:@"name" options:NSKeyValueObservingOptionNew
                              block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
                    
        }];

由于: (->表示持有关系) self -> KVOController -> _objectInfosMap -> self 造成了循环引用。所以此时应该使用:

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

<2> 为什么_FBKVOInfo的成员_controller是弱引用?

因为infos被_objectInfosMap强持有,如果不弱引用_controller,就会造成:
(->表示持有关系) self -> KVOController -> _objectInfosMap -> infos -> info -> _controller(KVOController)。
最终会造成KVOController无法释放,会影响到移除观察者。

(3)判断info是否存在
根据_FBKVOInfo中重写的- (BOOL)isEqual:(id)object 和 - (NSUInteger)hash 两个方法:

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

先对比的对象地址是否相同,最后再对比keyPath。

如果info存在,直接返回,防止重复观察。
如果info不存在,则添加到集合中。

(4)最后将被观察者(object)和 info传给_FBKVOSharedController 这个单利。

4、 _FBKVOSharedController 成员

@implementation _FBKVOSharedController
{
  NSHashTable<_FBKVOInfo *> *_infos;
  pthread_mutex_t _mutex;
}

_FBKVOSharedController这个单利中,也有两个成员变量:_infos 和 _mutex。
_mutex就不解释了,互斥锁,处理多线程问题。
_infos是一个NSHashTable的结构,这是因为NSHashTable和NSMapTable一样,同样支持value的内存管理策略设置。

5、 _FBKVOSharedController添加被观察者

- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
........
  // 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) {
    // 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];
  }
}

核心代码如下:

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

非常骚的有2点:(1)将当前的单利做为观察者。(2)将context设置为info。

- (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);
    //判断对象是否再_infos中存在,如果存在则返回,不存在返回nil
    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];
        }
      }
    }
  }
}

这个段代码正是系统KVO的回调方法,在此回调中,通过context从infos中判断获取_FBKVOInfo(真的骚)。
然后通过info获取controller,再通过controller获取observer。拿到observer之后,就可以把action以及系统本身的observeValueForKeyPath方法回调,回调给上层了。
分析到这里也就知道了为什么_FBKVOInfo要保存controller和context两个成员变量了。


六. 自动移除监听

因为FBKVOController 是一个实例,当控制器(self)销毁的时候,它也会被销毁。此时会走它的dealloc方法:

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

遍历_objectInfosMap,拿到每一个被观察者对应的info集合,然后交给shareController移除监听。

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

七、简易流程图