美文网首页
KVOController源码阅读

KVOController源码阅读

作者: Sweet丶 | 来源:发表于2021-04-09 11:12 被阅读0次

    在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<NSString *,id> * 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<NSString *, id> *)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<NSString *, id> *changeWithKeyPath = change;
              if (keyPath) {
                NSMutableDictionary<NSString *, id> *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<NSString *,id> * _Nonnull change) {
            NSLog(@"KVOController-----111");
        }];
        [self.KVOController observe:self.del keyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
            NSLog(@"KVOController-----222");
        }];
        [self.KVOController observe:self.del keyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
            NSLog(@"KVOController-----333");
        }];
    

    结果为:

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

    等待更新..

    KVO原理总结

    相关文章

      网友评论

          本文标题:KVOController源码阅读

          本文链接:https://www.haomeiwen.com/subject/ixhahltx.html