FBKVOController 源码解析
来自: 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; }
-
首先我们看到,这个对象持有一个 OSSpinLock 及一个 NSMapTable 。其中 OSSpinLock 即为自旋锁,当多个线程竞争相同的 critical section 时,起到保护作用。 NSMapTable 可能大家接触不是很多,我们在后文会详细介绍,这里大家可以先理解为一个高级的NSDictionary。
-
在构造函数中,首先将传入的observer进行 weak 持有,这主要为了避免 Retain Cycle 。
-
这一段的内容可能大家不太熟悉, NSPointerFunctionsOptions 简单来说就是定义 NSMapTable 中的key和value采用何种内存管理策略,包括 strong 强引用, weak 弱引用以及 copy (要支持NSCopying协议)
-
初始化自旋锁
接下来,使我们通过 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]; }
- 对于传入的参数,构建一个内部的FBKVOInfo数据结构
- 调用 [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自身标记的注释,有三处比较值得我们注意:
-
根据被观察的object获取其对应的 infos set 。这个主要作用在于避免多次对同一个keyPath添加多次观察,避免crash。 因为每调用一次 addObserverForKeyPath 就要有一个对应的 removeObserverForKey 。
-
从 infos set 判断是不是已经有了与此次info相同的观察。
-
如果以上都顺利通过,将观察的信息及关系注册到 _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]; }
- 代表所有的观察信息都首先由 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]; } } } } }
- 根据context上下文获取对应的KVOInfo
- 判断当前 info 的 observer 和 controller ,是否仍然存在(因为之前我们采用的weak持有)
- 根据 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>