KVOController源码阅读

在iOS开发中,KVO观察是我们经常要使用到的功能,但是当我们使用系统方式去实现时,存在着不好解决的问题:

  1. 需要在不使用时手动移除观察,一般在dealloc中移除, 那么如果对象被循环引用了,不会进入dealloc,那么此时KVO就未移除, 这个监听也就一直在.
  2. 如果被观察对象在移除所有观察者之前引用计数为0,那就会奔溃。
  3. 系统KVO观察的回调都是在同一个代理方法中,如果观察的属性多了就需要自己做区分,比较麻烦。
  4. 添加了多次观察会收到多次回调,移除也需要移除多次, 如果移除的次数多于添加的次数就会奔溃。

如果我们使用FaceBook的一个开源库KVOController就方便很多了,不需要手动移除,并且观察的回调是在对应的block中。

pod 'KVOController', '~> 1.2.0'
[self.KVOController observe:tableView keyPath:@"mj_footer.state" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary * change) {
    NSLog(@"observer=%@, object=%@, change=%@", observer, object, change);
}];

从上面代码可以看到使用起来非常简单,其中KVOController是使用分类给NBObject添加的属性,会自动初始化赋值,所以可以直接使用self.KVOController

一、怎么实现KVO观察结果在block中回调的?
  • 观察信息存储
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block{
// 1. 创建_FBKVOInfo对象,用于记录controller、block、options、keyPath等信息的
  _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];

  [self _observe:object info:info];
}

- (void)_observe:(id)object info:(_FBKVOInfo *)info{
// 从_objectInfosMap中以object为key找出这个对象所有的被KVO观察的信息infos。
// _objectInfosMap是一个NSMapTable映射表
  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // 检查infos中是否已经有info,比对的方式是isEqual:方法, _FBKVOInfo中重写了此方法,比对的是自身地址或者keypath是否一致
  _FBKVOInfo *existingInfo = [infos member:info];
  if (nil != existingInfo) {// 这个观察已经存在
    return;
  }
  // 将info添加到infos
  [infos addObject:info];
  [[_FBKVOSharedController sharedController] observe:object info:info];
}
  • _FBKVOSharedController添加KVO监听,收到KVO通知后从info中找出block调用
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info{
 // register info
  pthread_mutex_lock(&_mutex);
  [_infos addObject:info];
  pthread_mutex_unlock(&_mutex);

  // 添加系统KVO监听,把info作为context参数
  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
}

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary *)change
                       context:(nullable void *)context{
// 1. 从_infos中取出info
  _FBKVOInfo *info;
  {
    // _infos是NSHashTable,_FBKVOSharedController的成员变量
    pthread_mutex_lock(&_mutex);
    info = [_infos member:(__bridge id)context];
    pthread_mutex_unlock(&_mutex);
  }

// 2. 从info中取出controller
    FBKVOController *controller = info->_controller;
    if (nil != controller) {
      // 3.从controller中取出observer、block、keypath之后把keypath放到change中,调用_block
      id observer = controller.observer;
      if (info->_block) {
          NSDictionary *changeWithKeyPath = change;
          if (keyPath) {
            NSMutableDictionary *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
            [mChange addEntriesFromDictionary:change];
            changeWithKeyPath = [mChange copy];
          }
          info->_block(observer, object, changeWithKeyPath);
       } else {
          [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
        }
    }
}
二、怎么实现自动移除观察者的?
  1. 首先self对KVOController是强引用的(使用属性关联实现的),KVOController的observer是weak引用的。
// NSObject+FBKVOController中的属性KVOController
- (FBKVOController *)KVOController
{
  id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey);
  
  // lazily create the KVOController
  if (nil == controller) {
    controller = [FBKVOController controllerWithObserver:self];
    self.KVOController = controller;
  }
  
  return controller;
}
// FBKVOController的观察者
@property (nullable, nonatomic, weak, readonly) id observer;
  1. KVOController中的_objectInfosMap是observer所有观察的信息集合(sets),用来避免重复添加监听的。
  2. 在self销毁时,系统会自动移除对FBKVOController的引用,引起FBKVOController也要dealloc, 里面做的事情就是移除所有的observer相关的KVO监听,所以做到了自动移除.
- (void)dealloc{
  [self unobserveAll];
}

- (void)_unobserveAll{
  NSMapTable *objectInfoMaps = [_objectInfosMap copy];
  [_objectInfosMap removeAllObjects];

  _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
  for (id object in objectInfoMaps) {
    NSSet *infos = [objectInfoMaps objectForKey:object];
    [shareController unobserve:object infos:infos];
  }
}
// _FBKVOSharedController中的
- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos{
  for (_FBKVOInfo *info in infos) {
    [_infos removeObject:info];
  }
  // remove observer
  for (_FBKVOInfo *info in infos) {
    if (info->_state == _FBKVOInfoStateObserving) {
      [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
    }
    info->_state = _FBKVOInfoStateNotObserving;
  }
}
2.系统的KVO是怎么样引用观察者的,为什么对象dealloc时如果还有KVO监听者就会奔溃?
  1. 对象执行dealloc时,如果发现自己的isa指向的是kvo生成的子类,就会抛出这个异常NSInternalInconsistencyException,这点可以通过在观察者对象的dealloc中写下面的代码验证,(运行看效果需要在iOS12以下的设备运行)。
  2. 添加的观察者是assign或者_unsafe_unretain的,所以在observer销毁之后如果对象更改了属性,就发生野指针错误。
  3. iOS 12开始KVO不需要手动移除也不会奔溃,通知是iOS 10以下需要手动移除。
- (void)dealloc{
// 将self.Obj设置回原来的类就不会奔溃了,说明出现NSInternalInconsistencyException的原因是
//self执行dealloc后,self.Obj也会dealloc,但没有移除观察者,发现isa指向的是kvo生成的子类
    NSLog(@"self.Obj的类是:%@", object_getClass(self.Obj));
    object_setClass(self.Obj, [self.Obj class]);
    NSLog(@"self.Obj的类是:%@", object_getClass(self.Obj));
//    [self.Obj removeObserver:self forKeyPath:@"nickName"];
}
三、怎么实现避免多次收到KVO监听的?如果多次添加相同的监听是谁会收到block回调?

由一中的代码可以看到,如果对象的监听信息已经存在了,就不会再添加监听了。
_FBKVOInfo *existingInfo = [infos member:info];

  • 多次添加相同的KVO监听,结果是第一次会收到回调,因为后面的监听都添加失败了. (相同的监听指的是:observer、object、keyPath都相同)
    [self.KVOController observe:self.del keyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
        NSLog(@"KVOController-----111");
    }];
    [self.KVOController observe:self.del keyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
        NSLog(@"KVOController-----222");
    }];
    [self.KVOController observe:self.del keyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
        NSLog(@"KVOController-----333");
    }];

结果为:

KVOController-----111
四、用RAC实现KVO观察对比起来怎么样?

等待更新..

KVO原理总结

你可能感兴趣的:(KVOController源码阅读)