当前位置: 首页 > 工具软件 > KVOController > 使用案例 >

KVOController源码学习

夹谷烨赫
2023-12-01

相关类

_FBKVOInfo

{
@public
  __weak FBKVOController *_controller; // 防止观察者释放了然而_controller没有释放
  NSString *_keyPath;
  NSKeyValueObservingOptions _options;
  SEL _action;
  void *_context;
  FBKVONotificationBlock _block;
  _FBKVOInfoState _state;
}
- (instancetype)initWithController:(FBKVOController *)controller
                           keyPath:(NSString *)keyPath
                           options:(NSKeyValueObservingOptions)options
                             block:(nullable FBKVONotificationBlock)block
                            action:(nullable SEL)action
                           context:(nullable void *)context
{
  self = [super init];
  if (nil != self) {
    _controller = controller; // FBKVOController(包含观察者用于SEL的回调)
    _block = [block copy]; // 三种回调方式中的Block
    _keyPath = [keyPath copy]; // KVO的 forKeyPath
    _options = options;
    _action = action; // 三种回调方式中的SEL
    _context = context; // 三种回调方式中自己实现observeValueForKeyPath的context
  }
  return self;
}

复制代码
  • 相当于一个 Model 来记录相关属性
  • 重写 - (NSUInteger)hash 函数, 防止在集合类型中添加重复的 _keyPath
  • 重写 - (BOOL)isEqual:(id)object 进行对象的判等

_FBKVOSharedController

  • 事件的逻辑处理
// 看似很长其实也不多
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary<NSString *, id> *)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;
    // 由于 _FBKVOSharedController 是单例 不会释放,观察者可能释放掉了进而释放了FBKVOController
    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<NSString *, id> *changeWithKeyPath = change;
          // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
          if (keyPath) {
            NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
            [mChange addEntriesFromDictionary:change];
            changeWithKeyPath = [mChange copy];
          }
          //第一种处理方式 Block 回调
          info->_block(observer, object, changeWithKeyPath);
        } else if (info->_action) {
        // 写给编译器看的 去除"-Warc-performSelector-leaks"警告
        // 参考 https://nshipster.cn/pragma/
#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];
        }
      }
    }
  }
}
复制代码

FBKVOController

  • 接口类
  • 做一些校验类工作

实现流程

  1. NSObject 添加关联属性 KVOController (会对所观察的 ObjectRetain 操作), KVOControllerNonRetaining (不会对所观察的 ObjectRetain 操作)
  2. 通过 FBKVOController 接口类, 做一些参数校验,并生成 _FBKVOInfo 添加到 _FBKVOSharedController 这个单例当中
  3. _FBKVOSharedController 实现 - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context; 并根据 context 找到相关回调并执行

细节学习

这里用了 copy

- (instancetype)initWithController:(FBKVOController *)controller
                           keyPath:(NSString *)keyPath
                           options:(NSKeyValueObservingOptions)options
                             block:(nullable FBKVONotificationBlock)block
                            action:(nullable SEL)action
                           context:(nullable void *)context
{
  self = [super init];
  if (nil != self) { 
    _controller = controller;
    _block = [block copy];
    _keyPath = [keyPath copy]; //防止传入的对象是 NSMutableString
    _options = options;
    _action = action;
    _context = context;
  }
  return self;
}

复制代码

KVOController 不属于 UI 控件, 涉及多线程所以需要保证线程安全, 对在多线程访问的变量加锁

- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
  // 加锁
  pthread_mutex_lock(&_lock);

  NSMutableSet *infos = [_objectInfosMap objectForKey:object];

  // 检查是否添加过这个 keyPath, 防止重复添加
  _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;
  }

  // 懒加载
  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];
}

复制代码

更多锁参考 iOS 常见知识点(三):Lock

pthread_mutex_t 线程锁不属于 UIKitFoundation 对象生命周期需要手动管理

- (void)dealloc {
    pthread_mutex_destroy(&_mutex);
}
复制代码

NSMap​Table & NSHash​Table 对容器内的属性可以不增加引用计数

使用方法, 参考https://nshipster.cn/nshashtable-and-nsmaptable/

 类似资料: