FBKVOController 源碼解析

JefEusebio 8年前發布 | 17K 次閱讀 iOS開發 C語言 移動開發

來自: 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];
}
 本文由用戶 JefEusebio 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!