回顾
在前面的几篇博客中,已经介绍了KVO
的基本使用,如何自定义 KVO
,那么本篇博客将分析一下FBKVOController
这个优秀的KVO
三方库。
iOS底层探索之KVO(一)—KVO简介
iOS底层探索之KVO(二)—KVO原理分析
iOS底层探索之KVO(三)—自定义KVO
iOS底层探索之KVO(四)—自定义KVO
FBKVOController
是一个函数式编程实现,不用移除观察者者。
1. FBKVOController简单介绍
FBKVOController
是Facebook
开源的一个基于系统KVO
实现的框架。支持Objective-C
和Swift
语言。
GitHub地址
键值观察是一种特别有用的技术,用于在模型-视图-控制器应用程序中的层之间进行通信。
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来修饰
@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
的观察的意思。这一波操作,又是非常的细节,厉害了!
这里的object
参数传入的是什么呢?是 self
吗?
不是 self
是self.person
,why ?小朋友,你现在是否有很多问号?
在我们的印象中,使用
KVO
添加观察者传入的都是self
啊!但是靓仔,我们这里不是哦!
我们的VC
需要的是block
的回调,添加观察者是观察self.person
的属性变化,所以传入self.person
就好了。你内部怎么操作,我VC
不管,你只要把改变之后结果告诉我就好了,丢个block
的回调通知我VC
就 OK
了!
如图这里的self
是指前面的那个单例
,就是为了复用,就是:只要添加属性的观察都是使用这个单例,这里通过 keyPath
来区分,观察的是不同的属性。
2. 4 KVOController销毁
KVOController
的销毁,其实是内部帮我们实现了,所以不用我们手动去销毁。
- dealloc
- unobserveAll
- (void)unobserveAll
{
[self _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];
}
单例去调用内部的移除观察者方法
图中红色框起来的代码,其实就是调用的系统的移除观察者的方法removeObserver: forKeyPath :
由于
FBKVOController
的实例是VC持有的,所以我们的 VC被dealloc
销毁的时候FBKVOController
实例也就dealloc
了。在这里调用就相当于在 VC 中dealloc
中调用了移除是一样的。单例的生命周期是随着程序的生命周期的,不会销毁,只是传入的观察的对象在内部做了销毁,也就是移除操作,就是说 老铁 我不需要观察了,后续别给我发送消息了。
3. 通过gnustep探索KVO
kvo
与kvc
是属于Foundation
框架里面的,由于Foundation
相关的代码苹果并没有开源,对于它们的探索可以通过gnustep查看原理,gnustep
中有一些苹果早期底层的实现。
那么FBKVOController
分析就介绍到这了。
通过【gnustep
】具体的探索,这里就不过多的描述了,感兴趣的老铁,可以自行去下载Foundation
的源码,看看里面的实现,思路都是差不多的!
4. 总结
FBKVOController
使用了中介者
模式,通过函数式编程
的思想,把对属性的变化的观察,使用block
通知回调FBKVOController
注册,内部使用了单例,进行复用,通过keyPath
来区分,观察的是不同的属性。- 在控制器
dealloc
的时候隐式的把观察者移除,其实内部还是调用了系统的移除方法。
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹