美文网首页iOS
iOS底层探索之KVO(五)—FBKVOController分析

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

作者: 俊而不逊 | 来源:发表于2021-08-09 08:50 被阅读0次

    回顾

    在前面的几篇博客中,已经介绍了KVO的基本使用,如何自定义 KVO,那么本篇博客将分析一下FBKVOController这个优秀的KVO三方库。

    KVO

    iOS底层探索之KVO(一)—KVO简介
    iOS底层探索之KVO(二)—KVO原理分析
    iOS底层探索之KVO(三)—自定义KVO
    iOS底层探索之KVO(四)—自定义KVO

    FBKVOController是一个函数式编程实现,不用移除观察者者。

    1. FBKVOController简单介绍

    FBKVOControllerFacebook开源的一个基于系统KVO实现的框架。支持Objective-CSwift语言。
    GitHub地址

    KVOController

    键值观察是一种特别有用的技术,用于在模型-视图-控制器应用程序中的层之间进行通信。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会进行注册、移除以及回调的处理(回调包括blockaction以及兼容系统的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来修饰

    @property (nullable, nonatomic, weak, readonly) id observer;
    

    因为FBKVOController本身被观察者持有了,所以是weak类型的修饰。

    _objectInfosMap根据retainObserved进行NSMapTable内存管理/初始化配置,FBKVOController的成员变量。其中保存的是一个被观察者对应多个_FBKVOInfo(也就是被观察对象对应多个keyPath):

      NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
    

    _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中保存了一些相关的数据信息

    • 重写isEqualhash方法
    - (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吗?

    不是 selfself.person,why ?小朋友,你现在是否有很多问号?

    问号脸

    在我们的印象中,使用KVO添加观察者传入的都是 self啊!但是靓仔,我们这里不是哦!

    使用案例
    我们的VC需要的是block的回调,添加观察者是观察self.person的属性变化,所以传入self.person就好了。你内部怎么操作,我VC不管,你只要把改变之后结果告诉我就好了,丢个block 的回调通知我VCOK了!
    observe:(id)object info:
    如图这里的self是指前面的那个单例,就是为了复用,就是:只要添加属性的观察都是使用这个单例,这里通过 keyPath来区分,观察的是不同的属性。

    2. 4 KVOController销毁

    KVOController的销毁,其实是内部帮我们实现了,所以不用我们手动去销毁。

    • dealloc


      KVOController的dealloc
    • unobserveAll
    - (void)unobserveAll
    {
      [self _unobserveAll];
    }
    
    • _unobserveAll


      _unobserveAll
    • _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];
    }
    

    单例去调用内部的移除观察者方法

    unobserve:(id)object info:
    图中红色框起来的代码,其实就是调用的系统的移除观察者的方法removeObserver: forKeyPath :
    removeObserver: forKeyPath :

    由于FBKVOController的实例是VC持有的,所以我们的 VC被dealloc销毁的时候FBKVOController实例也就dealloc了。在这里调用就相当于在 VC 中dealloc中调用了移除是一样的。

    单例的生命周期是随着程序的生命周期的,不会销毁,只是传入的观察的对象在内部做了销毁,也就是移除操作,就是说 老铁 我不需要观察了,后续别给我发送消息了。

    移除观察者,苹果官方解释

    3. 通过gnustep探索KVO

    kvokvc是属于Foundation框架里面的,由于Foundation相关的代码苹果并没有开源,对于它们的探索可以通过gnustep查看原理,gnustep中有一些苹果早期底层的实现。

    gnustep官网首页
    那么FBKVOController分析就介绍到这了。

    通过【gnustep】具体的探索,这里就不过多的描述了,感兴趣的老铁,可以自行去下载Foundation的源码,看看里面的实现,思路都是差不多的!

    4. 总结

    • FBKVOController使用了中介者模式,通过函数式编程的思想,把对属性的变化的观察,使用 block通知回调
    • FBKVOController 注册,内部使用了单例,进行复用,通过 keyPath来区分,观察的是不同的属性。
    • 在控制器dealloc 的时候隐式的把观察者移除,其实内部还是调用了系统的移除方法。

    更多内容持续更新

    🌹 喜欢就点个赞吧👍🌹

    🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

    🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

    相关文章

      网友评论

        本文标题:iOS底层探索之KVO(五)—FBKVOController分析

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