iOS KVO -- FBKVOController分析

148 阅读4分钟

1. FBKVOController简单介绍

FBKVOController 是Facebook开源的一个基于系统KVO实现的框架。支持Objective-C和Swift语言。

键值观察是一种特别有用的技术,用于在模型-视图-控制器应用程序中的层之间进行通信。KVOController 建立在 Cocoa久经考验的键值观察实现之上。它提供了一个简单、现代的 API,这也是线程安全的。

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来修饰

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

  • 重写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的观察的意思。这一波操作,又是非常的细节,厉害了!

2.4 KVOController销毁

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

示例

#import "NSObject+FBKVOController.h"

[self.KVOController observe:_webView keyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];

[self.KVOController unobserve:_webView];