Crash 防禦方案(五):KVO

原文 : 與佳期的我的博客(gonghonglou.com)git

Apple 使用了 isa 混寫(isa-swizzling)來實現 KVO 。當觀察對象 A 時,KVO 機制動態建立一個新的名爲:NSKVONotifying_A 的新類,該類繼承自對象 A 的本類,Apple 還重寫了該類的 -class 方法,返回父類,即對象 A 的本類。且 KVO 爲 NSKVONotifying_A 重寫觀察屬性的 setter 方法,setter 方法會負責在調用原 setter 方法以前和以後,通知全部觀察對象屬性值的更改狀況。
isa 指針的做用:每一個對象都有 isa 指針,指向該對象的類,它告訴 Runtime 系統這個對象的類是什麼。因此對象註冊爲觀察者時,isa 指針指向新子類,那麼這個被觀察的對象就變成新子類的對象(或實例)了。 於是在該對象上對 setter 的調用就會調用已重寫的 setter,從而激活鍵值通知機制。github

咱們能夠經過斷點看到,被觀察者對象的 isa 指針已經變成了 NSKVONotifying_ 開頭的類: 安全

對於 KVO 使用不當的話很容易出現 Crash,好比添加和移除觀察不對應,重複 removeObserver: 或者移除一個不存在的觀察者就會形成 Crash,尤爲是在多線程操做時防不勝防:bash

2019-07-13 17:50:14.805177+0800 GHLCrashGuard_Example[77448:5047850] *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <GHLKVOViewController 0x7fd072c17720> for the key path "name" from <GHLTestObject 0x60000106c160> because it is not registered as an observer.'多線程

爲了不這種重複添加或者重複移除觀察形成的崩潰,能夠對 KVO 包裝一層。建立一個額外的觀察者對象,全部的添加觀察和移除觀察都經過這個額外的對象,這樣在添加和移除的時候就能夠作安全判斷了。 FaceBook 出品的 KVOController 就是作的這樣的事情。app

self.kvo = [FBKVOController controllerWithObserver:self];
[self.kvo observe:self.obj keyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {

   NSLog(@"oldName:%@", [change objectForKey:NSKeyValueChangeOldKey]);
   NSLog(@"newName:%@", [change objectForKey:NSKeyValueChangeNewKey]);
}];
複製代碼

FBKVOController 的關鍵主要就在如下三個方法上:ui

實例化

+ (instancetype)controllerWithObserver:(nullable id)observer; 建立 FBKVOController 對象,主要作了兩件事:一、存儲了觀察者 _observer,二、建立了 _objectInfosMap,用於存儲被觀察對象的信息。this

添加觀察

- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;spa

一、線程

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

主要在建立 _FBKVOInfo 對象,存儲 FBKVOController(存儲着觀察者 _observer) keyPath(觀察屬性) options(觀察時機) block(回調)

二、

- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
  // lock
  pthread_mutex_lock(&_lock);

  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // check for info existence
  _FBKVOInfo *existingInfo = [infos member:info];
  if (nil != existingInfo) {
    // observation info already exists; do not observe it again

    // unlock and return
    pthread_mutex_unlock(&_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
  pthread_mutex_unlock(&_lock);

  [[_FBKVOSharedController sharedController] observe:object info:info];
}
複製代碼

添加觀察者以前作的判斷,避免重複添加觀察。而且添加了 pthread_mutex_lock 互斥鎖保證線程安全。

三、

- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

  // register info
  pthread_mutex_lock(&_mutex);
  [_infos addObject:info];
  pthread_mutex_unlock(&_mutex);

  // add observer
  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

  if (info->_state == _FBKVOInfoStateInitial) {
    info->_state = _FBKVOInfoStateObserving;
  } else if (info->_state == _FBKVOInfoStateNotObserving) {
    // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
    // and the observer is unregistered within the callback block.
    // at this time the object has been registered as an observer (in Foundation KVO),
    // so we can safely unobserve it.
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
}
複製代碼

調用系統方法 addObserver: 添加觀察者,而且在這後判斷了 info->_state 若是是非觀察狀態則執行 removeObserver:

移除觀察

一、

- (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];
}
複製代碼

一樣是作了安全判斷,並經過 pthread_mutex_lock 鎖保證線程安全

二、

- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

  // unregister info
  pthread_mutex_lock(&_mutex);
  [_infos removeObject:info];
  pthread_mutex_unlock(&_mutex);

  // remove observer
  if (info->_state == _FBKVOInfoStateObserving) {
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
  info->_state = _FBKVOInfoStateNotObserving;
}
複製代碼

最後一步調用系統方法 removeObserver: 移除觀察者,info->_state 設置爲非觀察狀態

Demo 地址:GHLCrashGuard

後記

相關文章
相關標籤/搜索