天天看點

iOS底層探索之KVO(五)—FBKVOController分析

回顧

在前面的幾篇部落格中,已經介紹了

KVO

的基本使用,如何自定義

KVO

,那麼本篇部落格将分析一下

FBKVOController

這個優秀的

KVO

三方庫。

iOS底層探索之KVO(五)—FBKVOController分析

iOS底層探索之KVO(一)—KVO簡介

iOS底層探索之KVO(二)—KVO原理分析

iOS底層探索之KVO(三)—自定義KVO

iOS底層探索之KVO(四)—自定義KVO

FBKVOController

是一個函數式程式設計實作,不用移除觀察者者。

1. FBKVOController簡單介紹

FBKVOController

Facebook

開源的一個基于系統

KVO

實作的架構。支援

Objective-C

Swift

語言。

GitHub位址

iOS底層探索之KVO(五)—FBKVOController分析
鍵值觀察是一種特别有用的技術,用于在模型-視圖-控制器應用程式中的層之間進行通信。

KVOController

建立在

Cocoa

久經考驗的鍵值觀察實作之上。它提供了一個簡單、現代的

API

,這也是

線程安全

的。

KVOController

優點如下:

1.1 KVOController優點

  • 使用blocks、自定義操作或

    NSKeyValueObserving

    回調通知。
  • 不需要額外的移除觀察者
  • 在控制器 dealloc 的時候隐式的把觀察者移除。
  • 具有防止觀察者複活的特殊線程安全的保護機制
  • 有關 KVO 的更多資訊,請參閱 Apple 的鍵值觀察簡介。

1.2 FBKVOController 使用

FBKVOController

的使用起來非常的簡單,代碼很少,

FBKVOController

簡單使用如下代碼所示:

  • FBKVOController

    使用
self.person = [[JPPerson alloc] init];
    self.person.name = @"RENO";
    self.person.age = 18;
    self.person.mArray = [NSMutableArray arrayWithObject:@"1"];

    [self.kvoCtrl observe:self.person keyPath:@"age" options:0 action:@selector(jp_observerAge)];
    [self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
        NSLog(@"****%@****",change);
    }];
    [self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
        NSLog(@"****%@****",change);
    }];
           

代碼非常的簡潔,不用向我們之前使用系統的

KVO

那樣在

dealloc

裡面移除觀察者,這一波使用就很爽啊!

  • 懶加載初始化
#pragma mark - lazy
- (FBKVOController *)kvoCtrl{
    if (!_kvoCtrl) {
        _kvoCtrl = [FBKVOController controllerWithObserver:self];
    }
    return _kvoCtrl;
}
           

2. KVOController 實作分析

2.1 中介者模式

我們平時買房、租房都會找中介,通過中介可以更快更高效的找到合适的房子,也就很多事情中介幫我們去做了,不用我們自己去找房源。

KVOController

主要是使用了中介者模式,官方

kvo

使用麻煩的點在于使用需要三部曲。

KVOController

核心就是将三部曲進行了底層封裝,上層隻需要關心業務邏輯。

FBKVOController

會進行注冊、移除以及回調的處理(回調包括

block

action

以及相容系統的

observe

回調)。是對外暴露的互動類。使用

FBKVOController

分為兩步:
  • 使用

    controllerWithObserver

    初始化

    FBKVOController

    執行個體。
  • 使用

    observe:

    進行注冊。

2.2 FBKVOController 初始化

- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
  self = [super init];
  if (nil != self) {
    _observer = observer;
    NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
    pthread_mutex_init(&_lock, NULL);
  }
  return self;
}
           

_observer

是觀察者,是

FBKVOController

的屬性,用 weak來修飾

因為

FBKVOController

本身被觀察者持有了,是以是

weak

類型的修飾。

_objectInfosMap

根據

retainObserved

進行

NSMapTable

記憶體管理/初始化配置,

FBKVOController

的成員變量。其中儲存的是一個被觀察者對應多個

_FBKVOInfo

(也就是被觀察對象對應多個

keyPath

):

_FBKVOInfo

是放在

NSMutableSet

中的,說明是去重的。

2.3 FBKVOController 注冊

- (void)observe:(nullable 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;
  }

  // create info
  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];

  // observe object with info
  [self _observe:object info:info];
}
           
  • 首先第一步就是做一些判斷,容錯判斷。
  • 構造

    _FBKVOInfo

    ,儲存

    FBKVOController

    、keyPath、

    options

    以及

    block

  • 調用

    _observe:(id)object info:(_FBKVOInfo *)info

  • _FBKVOInfo
@implementation _FBKVOInfo
{
@public
  __weak FBKVOController *_controller;
  NSString *_keyPath;
  NSKeyValueObservingOptions _options;
  SEL _action;
  void *_context;
  FBKVONotificationBlock _block;
  _FBKVOInfoState _state;
}
           

_FBKVOInfo

中儲存了一些相關的資料資訊

  • 重寫

    isEqual

    hash

    方法
- (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];
}
           
隻要

_keyPath

相同就認為是同一對象
  • _observe: info:
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
  // lock
  pthread_mutex_lock(&_lock);

  //從TableMap中擷取 object(被觀察者) 對應的 set
  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // check for info existence
  //判斷對應的keypath info 是否存在
  _FBKVOInfo *existingInfo = [infos member:info];
  if (nil != existingInfo) {
    //存在直接傳回,這裡就相當于對于同一個觀察者排除了相同的keypath
    // observation info already exists; do not observe it again

    // unlock and return
    pthread_mutex_unlock(&_lock);
    return;
  }

  // lazilly create set of infos
  //TableMap資料為空進行建立設定
  if (nil == infos) {
    infos = [NSMutableSet set];
    //<被觀察者 - keypaths info>
    [_objectInfosMap setObject:infos forKey:object];
  }

  // add info and oberve
  //keypaths info添加 keypath info
  [infos addObject:info];

  // unlock prior to callout
  pthread_mutex_unlock(&_lock);
  //注冊
  [[_FBKVOSharedController sharedController] observe:object info:info];
}
           
  • 首先判斷

    kayPath

    是否已經被注冊了,注冊了直接傳回,這裡也就是進行了去重的處理,這一波操作就非常細節。
  • 将構造的

    _FBKVOInfo

    資訊添加進

    _objectInfosMap

    中。
  • 調用

    _FBKVOSharedController

    進行真正的注冊。
  • member:

    說明

    member

    會調用到

    _FBKVOInfo中的hash

    以及

    isEqual

    進行判斷對象是否存在,也就是判斷

    keyPath

    對應的對象是否存在。
這裡注冊

[[_FBKVOSharedController sharedController] observe:object info:info]

是使用了單例
為什麼這裡使用

單例

呢?而不是在外面的調用初始化的時候使用單例呢?
這方法裡面使用單例,下次再次使用就不會重複建立了,就是相當于保活了,我們在

VC

中使用的是

FBKVOController

的執行個體對象,會随着

VC

的銷毀而銷毀,這個單例觀察者會在内部移除,移除不是銷毀的意思,隻是告訴這個單例,移除對某個對象的觀察,例如觀察了

self.person

的屬性,最後的

dealloc

是移除對

self.person

的觀察的意思。這一波操作,又是非常的細節,厲害了!

這裡的

object

參數傳入的是什麼呢?是

self

嗎?

不是

self

self.person

,why ?小朋友,你現在是否有很多問号?

iOS底層探索之KVO(五)—FBKVOController分析
在我們的印象中,使用

KVO

添加觀察者傳入的都是

self

啊!但是靓仔,我們這裡不是哦!
iOS底層探索之KVO(五)—FBKVOController分析

我們的

VC

需要的是

block

的回調,添加觀察者是觀察

self.person

的屬性變化,是以傳入

self.person

就好了。你内部怎麼操作,我

VC

不管,你隻要把改變之後結果告訴我就好了,丢個

block

的回調通知我

VC

OK

了!

iOS底層探索之KVO(五)—FBKVOController分析

如圖這裡的

self

是指前面的那個

單例

,就是為了複用,就是:隻要添加屬性的觀察都是使用這個單例,這裡通過

keyPath

來區分,觀察的是不同的屬性。

2. 4 KVOController銷毀

KVOController

的銷毀,其實是内部幫我們實作了,是以不用我們手動去銷毀。
  • dealloc
    iOS底層探索之KVO(五)—FBKVOController分析
  • unobserveAll
- (void)unobserveAll
{
  [self _unobserveAll];
}
           
  • _unobserveAll
    iOS底層探索之KVO(五)—FBKVOController分析
  • _unobserve:(id)object info:
- (void)_unobserve:(id)object info:(_FBKVOInfo *)info
{
  // lock
  pthread_mutex_lock(&_lock);

  // get observation infos
  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // lookup registered info instance
  _FBKVOInfo *registeredInfo = [infos member:info];

  if (nil != registeredInfo) {
    [infos removeObject:registeredInfo];

    // remove no longer used infos
    if (0 == infos.count) {
      [_objectInfosMap removeObjectForKey:object];
    }
  }

  // unlock
  pthread_mutex_unlock(&_lock);

  // unobserve
  [[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
}
           
單例去調用内部的移除觀察者方法
iOS底層探索之KVO(五)—FBKVOController分析

圖中紅色框起來的代碼,其實就是調用的系統的移除觀察者的方法

removeObserver: forKeyPath :

iOS底層探索之KVO(五)—FBKVOController分析
由于

FBKVOController

的執行個體是VC持有的,是以我們的 VC被

dealloc

銷毀的時候

FBKVOController

執行個體也就

dealloc

了。在這裡調用就相當于在 VC 中

dealloc

中調用了移除是一樣的。
單例的生命周期是随着程式的生命周期的,不會銷毀,隻是傳入的觀察的對象在内部做了銷毀,也就是移除操作,就是說 老鐵 我不需要觀察了,後續别給我發送消息了。
iOS底層探索之KVO(五)—FBKVOController分析

3. 通過gnustep探索KVO

kvo

kvc

是屬于

Foundation

架構裡面的,由于

Foundation

相關的代碼蘋果并沒有開源,對于它們的探索可以通過gnustep檢視原理,

gnustep

中有一些蘋果早期底層的實作。

iOS底層探索之KVO(五)—FBKVOController分析

那麼

FBKVOController

分析就介紹到這了。

通過【

gnustep

】具體的探索,這裡就不過多的描述了,感興趣的老鐵,可以自行去下載下傳

Foundation

的源碼,看看裡面的實作,思路都是差不多的!

4. 總結

  • FBKVOController

    使用了

    中介者

    模式,通過

    函數式程式設計

    的思想,把對屬性的變化的觀察,使用

    block

    通知回調
  • FBKVOController

    注冊,内部使用了單例,進行複用,通過

    keyPath

    來區分,觀察的是不同的屬性。
  • 在控制器

    dealloc

    的時候隐式的把觀察者移除,其實内部還是調用了系統的移除方法。

更多内容持續更新

🌹 喜歡就點個贊吧👍🌹

🌹 覺得有收獲的,可以來一波,收藏+關注,評論 + 轉發,以免你下次找不到我😁🌹

🌹歡迎大家留言交流,批評指正,互相學習😁,提升自我🌹