天天看点

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

回顾

在前面的几篇博客中,已经介绍了

KVO

的基本使用,如何自定义

KVO

,那么本篇博客将分析一下

FBKVOController

这个优秀的

KVO

三方库。

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

iOS底层探索之KVO(一)—KVO简介

iOS底层探索之KVO(二)—KVO原理分析

iOS底层探索之KVO(三)—自定义KVO

iOS底层探索之KVO(四)—自定义KVO

FBKVOController

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

1. FBKVOController简单介绍

FBKVOController

Facebook

开源的一个基于系统

KVO

实现的框架。支持

Objective-C

Swift

语言。

GitHub地址

iOS底层探索之KVO(五)—FBKVOController分析
键值观察是一种特别有用的技术,用于在模型-视图-控制器应用程序中的层之间进行通信。

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

会进行注册、移除以及回调的处理(回调包括

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

因为

FBKVOController

本身被观察者持有了,所以是

weak

类型的修饰。

_objectInfosMap

根据

retainObserved

进行

NSMapTable

内存管理/初始化配置,

FBKVOController

的成员变量。其中保存的是一个被观察者对应多个

_FBKVOInfo

(也就是被观察对象对应多个

keyPath

):

_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

的观察的意思。这一波操作,又是非常的细节,厉害了!

这里的

object

参数传入的是什么呢?是

self

吗?

不是

self

self.person

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

iOS底层探索之KVO(五)—FBKVOController分析
在我们的印象中,使用

KVO

添加观察者传入的都是

self

啊!但是靓仔,我们这里不是哦!
iOS底层探索之KVO(五)—FBKVOController分析

我们的

VC

需要的是

block

的回调,添加观察者是观察

self.person

的属性变化,所以传入

self.person

就好了。你内部怎么操作,我

VC

不管,你只要把改变之后结果告诉我就好了,丢个

block

的回调通知我

VC

OK

了!

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

如图这里的

self

是指前面的那个

单例

,就是为了复用,就是:只要添加属性的观察都是使用这个单例,这里通过

keyPath

来区分,观察的是不同的属性。

2. 4 KVOController销毁

KVOController

的销毁,其实是内部帮我们实现了,所以不用我们手动去销毁。
  • dealloc
    iOS底层探索之KVO(五)—FBKVOController分析
  • unobserveAll
- (void)unobserveAll
{
  [self _unobserveAll];
}
           
  • _unobserveAll
    iOS底层探索之KVO(五)—FBKVOController分析
  • _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];
}
           
单例去调用内部的移除观察者方法
iOS底层探索之KVO(五)—FBKVOController分析

图中红色框起来的代码,其实就是调用的系统的移除观察者的方法

removeObserver: forKeyPath :

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

FBKVOController

的实例是VC持有的,所以我们的 VC被

dealloc

销毁的时候

FBKVOController

实例也就

dealloc

了。在这里调用就相当于在 VC 中

dealloc

中调用了移除是一样的。
单例的生命周期是随着程序的生命周期的,不会销毁,只是传入的观察的对象在内部做了销毁,也就是移除操作,就是说 老铁 我不需要观察了,后续别给我发送消息了。
iOS底层探索之KVO(五)—FBKVOController分析

3. 通过gnustep探索KVO

kvo

kvc

是属于

Foundation

框架里面的,由于

Foundation

相关的代码苹果并没有开源,对于它们的探索可以通过gnustep查看原理,

gnustep

中有一些苹果早期底层的实现。

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

那么

FBKVOController

分析就介绍到这了。

通过【

gnustep

】具体的探索,这里就不过多的描述了,感兴趣的老铁,可以自行去下载

Foundation

的源码,看看里面的实现,思路都是差不多的!

4. 总结

  • FBKVOController

    使用了

    中介者

    模式,通过

    函数式编程

    的思想,把对属性的变化的观察,使用

    block

    通知回调
  • FBKVOController

    注册,内部使用了单例,进行复用,通过

    keyPath

    来区分,观察的是不同的属性。
  • 在控制器

    dealloc

    的时候隐式的把观察者移除,其实内部还是调用了系统的移除方法。

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

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

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