FBKVOController 源码解析

JefEusebio 11年前

来自: http://satanwoo.github.io/2016/02/27/FBKVOController/

开发过iOS的app已经不计其数了,在不同的项目中采用的架构也各不相同,有传统的 MVC ,简化的 VIPER ,以及一些简单的 MVVM 。

这其中,我最不推荐的就是 VIPER ,谁写谁知道,,绝对是增加了项目的复杂性。 MVVM 由于自己总是受限于传统的 Object-Oriented 的思路,总是想不出真正的Functional Programming的代码,因此,绝大多数情况,写着写着都回归到了 MVC 。

其实,相较于网上大家总喜欢提到的 Massive View Controller 问题,我更想说的是这种传统架构中对于信息流的不友好。

在一个典型的iOS的问题中,我们的代码执行流程,通常都是从View Controller的 生命周期 开始,如果是一个完全基于顺序执行的应用,那整个app的信息流是 单向可跟踪的 。但是往往事情并不会那么简单,我们会包含至少如下这些潜在打乱信息流的 坏蛋

  • Delegate回调
  • NSNotification
  • UIView控件的Target-Action
  • KVO

在这里,你可能会以为我想谈谈 ReactiveCocoa 和 RxSwift ,那你错啦,那个开源项目我暂时还没有能力去深究,所以我想从KVO事件入手,读一读非死book出品的 FBKVOController 。

FBKVOController

简单来说,FBKVOController是对KVO机制的一层封装,同时提供了线程安全的特性和并对如下这个 臭名昭著 的函数进行了封装,提供了干净的block的回调,避免了处理这个函数的逻辑散落的到处都是。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

源码分析

整个项目的结构非常简单,包含如下四个文件:

  • FBKVOController.h/.m
  • NSObject+FBKVOController.h/.m

其中, NSObject+FBKVOController 只是通过 AssociateObject 给NSObject提供了一个 retain 及一个 非retain 型的KVOController。

这两种不同类型的KVOController有啥区别,我们稍后再提,我们将重点投向 FBKVOController 这个文件。

打开这个 FBKVOController.m 文件,哎呀,600多行文件,有点蛋疼。没事,配合头文件粗略扫一眼以后,可以发现其中很多方法都是 convenience method 。

简单剥离一下数据结构以后,我们可以发现,主要的数据结构有如下三个。

  • FBKVOInfo
  • FBKVOSharedController
  • FBKVOController

FBKVOController

既然我们前面通过 NSObject+FBKVOController 知道了每个对象都会有其对应的 FBKVOController ,那我们就先来看看这个类吧。

//1.  @implementation FBKVOController  {    NSMapTable *_objectInfosMap;    OSSpinLock _lock;  }    //2.  - (instancetype)initWithObserver:(id)observer retainObserved:(BOOL)retainObserved  {    self = [super init];    if (nil != self) {      // 2.      _observer = observer;        // 3.      NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;      _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];        // 4.      _lock = OS_SPINLOCK_INIT;    }    return self;  }
  1. 首先我们看到,这个对象持有一个 OSSpinLock 及一个 NSMapTable 。其中 OSSpinLock 即为自旋锁,当多个线程竞争相同的 critical section 时,起到保护作用。 NSMapTable 可能大家接触不是很多,我们在后文会详细介绍,这里大家可以先理解为一个高级的NSDictionary。

  2. 在构造函数中,首先将传入的observer进行 weak 持有,这主要为了避免 Retain Cycle 。

  3. 这一段的内容可能大家不太熟悉, NSPointerFunctionsOptions 简单来说就是定义 NSMapTable 中的key和value采用何种内存管理策略,包括 strong 强引用, weak 弱引用以及 copy (要支持NSCopying协议)

  4. 初始化自旋锁

接下来,使我们通过 FBKVOController 来对一个对象的某个或者某些keypath进行观察。

- (void)observe:(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;    }      // 1. create info    _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];      // 2. observe object with info    [self _observe:object info:info];  }
  1. 对于传入的参数,构建一个内部的FBKVOInfo数据结构
  2. 调用 [self _observe:object info:info];

接下来,我们来跟踪一下 [self _observe:object info:info]; ,内容如下:

- (void)_observe:(id)object info:(_FBKVOInfo *)info  {    // lock    OSSpinLockLock(&_lock);      // 1.    NSMutableSet *infos = [_objectInfosMap objectForKey:object];      // 2.     _FBKVOInfo *existingInfo = [infos member:info];    if (nil != existingInfo) {      NSLog(@"observation info already exists %@", existingInfo);        // unlock and return      OSSpinLockUnlock(&_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    OSSpinLockUnlock(&_lock);      // 3.    [[_FBKVOSharedController sharedController] observe:object info:info];  }

抛开非死book自身标记的注释,有三处比较值得我们注意:

  1. 根据被观察的object获取其对应的 infos set 。这个主要作用在于避免多次对同一个keyPath添加多次观察,避免crash。 因为每调用一次 addObserverForKeyPath 就要有一个对应的 removeObserverForKey 。

  2. 从 infos set 判断是不是已经有了与此次info相同的观察。

  3. 如果以上都顺利通过,将观察的信息及关系注册到 _FBKVOSharedController 中。

至此,FBKVOController的任务基本都结束, unObserve 相关的任务逻辑大同小异,不再赘述。

FBKVOSharedController

初次看到这个类的时候,我的脑海中浮现了两个问题,FBKVOSharedController是干嘛的?为什么FBKVOController还需要将观察的信息转交呢?

其实我个人觉得这一层不是必要的,但是按照非死book的理念来说就是将所有的观察信息统一交由一个 FBKVOSharedController 的 单例 进行维护。如果大家读过非死book出品的 Flux 架构,也会发现,非死book经常喜欢维护一个类似于中间件的注册表,在这里, FBKVOSharedController 承担的也是类似的职责。

于是,通过如下方法,我们像使用注册表一样将对KVOInfo注册。

- (void)observe:(id)object info:(_FBKVOInfo *)info  {    if (nil == info) {      return;    }      // register info    OSSpinLockLock(&_lock);    [_infos addObject:info];    OSSpinLockUnlock(&_lock);      // 1.    [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];  }
  1. 代表所有的观察信息都首先由 FBKVOSharedController 进行接受,随后进行转发。

实现 observeValueForKeyPath:ofObject:Change:context
来接收通知。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context  {    NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);      _FBKVOInfo *info;      {      // 1.       OSSpinLockLock(&_lock);      info = [_infos member:(__bridge id)context];      OSSpinLockUnlock(&_lock);    }      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) {            info->_block(observer, object, change);          } 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. 根据context上下文获取对应的KVOInfo
  2. 判断当前 info 的 observer 和 controller ,是否仍然存在(因为之前我们采用的weak持有)
  3. 根据 info 的 block 或者 selector 或者 override 进行消息转发。

到这里, FBKVOController 整体的实现就介绍完了,怎么样,是不是局部看自己都会实现,但是一结合起完整的设计思路,就觉得,不亏是非死book呢。

NSMapTable

之前我们在前文中提到了 NSMapTable ,现在我们来详细介绍他一下。

我们在平常的开发中都使用过 NSDictionary 或者 NSMutableDictionary ,但是这两种数据结构有其的局限性。

以 NSDictionary 为例, NSDictionary 将 key 的 hash 值作为索引,存储对应的 value 。因此, key 的要求是不能更改。所以, NSDictionary 为了确保安全,对于 key 采用了 copy 的策略。

默认情况下,支持 NSCopying 协议的类型都可以作为key。但是考虑到copy带来的开销,一般情况下我们都使用简单的诸如数字或者字符串作为key。

那么,如果要使用 Object 作为key,想构建 Object to Object 的关系怎么办呢?这个时候就用到 NSMapTable 。我们可以通过NSFunctionsPointer来分别定义对key和value的储存关系,简单可以分类为 strong , weak 以及 copy 。而当利用 object 作为key的时候,可以定义评判相等的标准,如: use shifted pointer hash and direct equality, object description或者size 。

具体你需要去override如下几种方法:

// pointer personality functions  @property (nullable) NSUInteger (*hashFunction)(const void *item, NSUInteger (* __nullable size)(const void *item));  @property (nullable) BOOL (*isEqualFunction)(const void *item1, const void*item2, NSUInteger (* __nullable size)(const void *item));  @property (nullable) NSUInteger (*sizeFunction)(const void *item);  @property (nullable) NSString * __nullable (*descriptionFunction)(const void *item);

在 FBKVOController 自定义的可以作为key的结构 FBKVOInfo ,就复写了

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