| 注册
请输入搜索内容

热门搜索

Java Linux MySQL PHP JavaScript Hibernate jQuery Nginx
JefEusebio
9年前发布

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

在这里,你可能会以为我想谈谈 ReactiveCocoaRxSwift ,那你错啦,那个开源项目我暂时还没有能力去深究,所以我想从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>

 本文由用户 JefEusebio 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
 转载本站原创文章,请注明出处,并保留原始链接、图片水印。
 本站是一个以用户分享为主的开源技术平台,欢迎各类分享!
 本文地址:https://www.open-open.com/lib/view/open1456572796796.html
iOS开发 C语言 移动开发