转载

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事件入手,读一读Facebook出品的 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]; } 

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

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

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

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

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

FBKVOSharedController

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

其实我个人觉得这一层不是必要的,但是按照Facebook的理念来说就是将所有的观察信息统一交由一个 FBKVOSharedController 的 单例 进行维护。如果大家读过Facebook出品的 Flux 架构,也会发现,Facebook经常喜欢维护一个类似于中间件的注册表,在这里, 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 整体的实现就介绍完了,怎么样,是不是局部看自己都会实现,但是一结合起完整的设计思路,就觉得,不亏是Facebook呢。

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]; } 
原文  http://satanwoo.github.io/2016/02/27/FBKVOController/
正文到此结束
Loading...