读源码涨姿势之优雅KVO实现

如果说书籍是人类进步的阶梯,那么优秀的开源代码就是程序员提升的桥梁。研读源码可以学习其中的框架和模式, 代码技巧, 算法等,然后不断总结运用,最终这些会变成自己的东西,编程水平自然也提高了。

1、FBKVOController优势

FBKVOController是Facebook开源的接口设计优雅的KVO框架,线程安全的KVO ,它提供block解决了以前使用KVO时代码散乱的缺点,方便实用,github地址。

  1. 不需要手动移除观察者;当FBKVOController对象被释放时, 观察者被隐式移除
  2. 实现 KVO 与事件发生处的代码上下文相同,不需要跨方法传参数;依旧不懂
  3. 使用 block 来替代方法能够减少使用的复杂度,提升使用 KVO 的体验;block或者selector的方式,方便使用
  4. 每一个 keyPath 会对应一个属性,不需要在 block 中使用 if 判断 keyPath;一个keyPath对应一个SEL或者block,不需要统一的observeValueForKeyPath方法里写if判断
  5. 可以同时对一个对象的多个属性进行监听,写法简洁
2、FBKVOController的类介绍

FBKVOController的核心代码一共4个类:

  • NSObject+FBKVOController.h
  • NSObject+FBKVOController.m
  • FBKVOController.h
  • FBKVOController.m

NSObject+FBKVOController.m是一个Category。通过AssociateObject给NSObject提供一个KVOControllerKVOControllerNonRetaining的KVOController属性。

FBKVOController.m一共可以分为3部分:

  • _FBKVOInfo
  • _FBKVOSharedController
  • FBKVOController
  1. 其中_FBKVOInfo主要是对需要观测的信息的包装,没有任何业务逻辑,只是一个简单的Model,主要是将以下的实例变量封装到对象中,包含了action、block、options等等,改类中重写了hash,isEqual等方法,用来判断是否两个Info是否一致。(后续会有用)
@implementation _FBKVOInfo 
{ 
@public 
__weak FBKVOController *_controller; 
NSString *_keyPath; 
NSKeyValueObservingOptions _options; 
SEL _action; 
void *_context; 
FBKVONotificationBlock _block; 
_FBKVOInfoState _state; 
} 
  1. FBKVOController是核心类,包含MapTablepthread_mutex_lock,其中_objectInfosMap是存储一个对象对应的KVOInfo的映射关系,也就是说这里 *> 中的id就是对象,
    MutableSet就是KVOInfos,各种键值观测的包装。
@interface FBKVOController : NSObject

#pragma mark - Initialize  初始化方法
+ (instancetype)controllerWithObserver:(nullable id)observer;
//参数:observer是观察者,retainObserved:表示是否强引用被观察的对象,这是一个全能初始化的对象方法,其他初始化方法内部均调用该方法
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;
//该初始化方法内部调用上一个初始化方法,默认强引用被观察的对象
- (instancetype)initWithObserver:(nullable id)observer;

#pragma mark - Observe  监听方法
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action;
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//监听多个属性的方法
- (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;
- (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options action:(SEL)action;
- (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

@end

@implementation FBKVOController
{
  NSMapTable *> *_objectInfosMap;
  pthread_mutex_t _lock;
}
...//此处省略源码,可以下载源码自己去看
@end
  1. FBKVOSharedController是一个实际操作类,负责将FBKVOController发送过来的信息转发给系统的KVO处理。
@implementation _FBKVOSharedController
{
  NSHashTable<_FBKVOInfo *> *_infos;
  pthread_mutex_t _mutex;
}

4.NSObject+FBKVOController
NSObject+FBKVOController 分类比较简单,它主要通过runtime方法,以懒加载的形式给 NSObject ,创建并关联一个 FBKVOController 的对象。

@interface NSObject (FBKVOController)
@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
@end

3、键值观测的源码流程

先来看全能初始化方法内部的实现,该方法对三个实例变量_observer(观察者),_objectInfosMap(NSMapTable,被监听对象->被监听属性集合之间的映射关系),pthread_mutex_init(互斥锁):

//全能初始化方法
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
  self = [super init];
  if (nil != self) {
      
    //观察者
    _observer = observer;

//NSMapTable中的key可以为对象,而且可以对其中的key和value弱引用
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
      
//对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER
//对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy
    pthread_mutex_init(&_lock, NULL);
  }
  return self;
}

这里请先思考以下问题:

  • 属性observer为何使用weak,它和哪个对象之间会导致循环引用问题,是如何导致循环引用问题的?
  • 为何不使用字典来保存被监听对象和被监听属性集合之间的关系?
  • NSDictionary的局限性有哪些?NSMapTable相对字典,有哪些优点?
  • 互斥锁是为了保证哪些数据的线程安全?

带着这些问题我们来看FBKVOController内部是如何实现监听的,这里我们只看带Block回调的一个监听方法,其他几个方法和这个方法内部实现是相同的。下面的方法内部做了如下工作:

  1. 传入的参数keyPath,block为空时,程序闪退,同时报出误提示;
  2. 对传入参数为空的判读;
  3. 利用传入的参数创建_FBKVOInfo对象;
  4. 调用内部私有方法实现注册监听;
#pragma mark API -

- (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];
}

该私有方法内部并没有实现真正的注册监听,这个函数- (void)_observe:(id)object info:(_FBKVOInfo *)info是FBKVOController的,首先是加锁,防止读写干扰。然后我们查找一下这个object对应的MutableSet,如果有对应的KVOInfo的话,那么就不需要再添加入_objectInfosMap中了;如果没有,则创建info,并且加入_objectInfosMap中。最后解锁,将object传给_FBKVOSharedController处理。

内部实现思路:

  • 对当前线程访问的数据_objectInfosMap进行加锁;
  • 根据被监听对象object到_objectInfosMap取出被监听的属性信息对象集合infos;
  • 判断被监听的属性对象info是否存在集合中;
  • 如果已经存在,则不需要再次添加监听,防止多次监听;
  • 如果获取的集合infos为空,则建存放_FBKVOInfo对象的集合infos,保存映射关系:object->infos;
  • 将被监听的信息_FBKVOInfo对象存到集合infos中;
  • 解锁,其他线程可以访问该数据;
  • 调用_FBKVOSharedController 的方法实现监听
- (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];
}

这个- (void)observe:(id)object info:(nullable _FBKVOInfo *)info函数应该是调用系统的addObserver,添加观察者。其中[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];这句话就是系统的KVO观测,object添加观测者,这里添加的是FBKVOShare,最后KVO的相应函数也在这里,正好呼应。

_FBKVOSharedController:

- (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];
  }
}

这里是KVO的相应函数,有木有很熟悉?这里将当初我们传入的一些action或者block执行以下,完美。

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary *)change
                       context:(nullable void *)context
{
  NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);

  _FBKVOInfo *info;

  {
    // lookup context in registered infos, taking out a strong reference only if it exists
    pthread_mutex_lock(&_mutex);
    info = [_infos member:(__bridge id)context];
    pthread_mutex_unlock(&_mutex);
  }

  if (nil != info) {

    // take strong reference to controller
    FBKVOController *controller = info->_controller;
    if (nil != controller) {

      // take strong reference to observer
      id observer = controller.observer;
      if (nil != observer) {

        // dispatch custom block or action, fall back to default action
        if (info->_block) {
          NSDictionary *changeWithKeyPath = change;
          // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
          if (keyPath) {
            NSMutableDictionary *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
            [mChange addEntriesFromDictionary:change];
            changeWithKeyPath = [mChange copy];
          }
          info->_block(observer, object, changeWithKeyPath);
        } else if (info->_action) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
          [observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
        } else {
          [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
        }
      }
    }
  }
循环引用的分析

VC1持有_kvoCtrl,_kvoCtrl持有一个_objectInfosMap,这是一个可以存放弱指针的NSDictionary,这个函数[_objectInfosMap setObject:infos forKey:object];就是将object和其需要监听的info加入map中。故VC1持有KVOCtrl,KVOCtrl持有map,map持有VC2,也就是VC1持有VC2。这是要如果我们VC2里观测VC1,就会VC2持有VC1。造成循环引用。

FBKVOController是线程安全的,相对于系统的KVO而言,使用起来更方便,安全,简洁。
1.NSHashTable和NSMapTable的使用;
2.互斥锁pthread_mutex_t的使用
3.FBKVOController和Observer之间循环引用的形成和解决;
4.FBKVOController和_FBKVOInfo之间循环引用的形成和解决;

参考地址:
https://www.jianshu.com/p/77b1d627780e
https://www.jianshu.com/p/1f7d70ff2002

你可能感兴趣的:(读源码涨姿势之优雅KVO实现)