前言
FBKVOController是对系统的KVO的一个封装,通过合理利用对象的生命周期实现了自动销毁,并合理地保存观察者与被观察之间的关系
再探究他之前,需要回顾下KVO的一些基本知识,前面讲了添加监听方法如下所示:
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
该方法会对被观察者添加一个监听对象observer,通过该监听对象observer来观察其某一个键值keyPath,因此可以引出观察者、被观察者的关系
1.当被观察被某一个对象observer观察时,可以观察其多个键值,可以有多个回调
2.多个被观察者可以被同一个对象observer观察,因此每一个观察者可以监听多个被观察对象的多个属性
3.每个被观察者,可以添加多个observer对象(也就意味着被多个对象observer观察),因此当observer销毁时不能移除被观察者的所有监听,只能移除监听对象observer的监听
其关系如下图所示:
FBKVOController探究
FBKVOController中一共有三个核心类_FBKVOInfo、_FBKVOSharedController、FBKVOController
平时使用FBKVOController的方式如下,也就是给观察者所在的类持有KVOControler对象,因此当被观察对象释放时,KVOController对象也会随之释放并调用其dealloc回调,在里面可以移除与其相关的监听
// create KVO controller with observer
self.KVOController = = [FBKVOController controllerWithObserver:self];
// observe clock date property
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {
}];
下面通过介绍三个核心类来一步一步解开FBKVO的原理
_FBKVOInfo
如下面源码所示,_FBKVOInfo定义了下面几个参数:
_controller:观察者持有的KVOController用于检查该对象是否已经释放
_keyPath: 观察的键值
_options、_context:通过addObserver传递的枚举类型和_context
_action、_block:当触发KVO时的回调
_state:_FBKVOInfo所处的枚举状态
可以猜测到KVOInfo为一个监听对象的载体,可以参考最上面的图,其实际为观察者和被观察者行程的多对多关系的基本载体,即:里面有监听必须的回调方法和对应的keyPath
{
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}
并且其重写了hash和isEqual方法,因此在set里面,相同keyPath的KVOInfo会被认为相同(NSHashTables是可以使用指针来判断,结果可能不一样)
- (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];
}
FBKVOController与添加监听
FBKVOController就是被观察者持有的属性对象KVOController,为调用者独立出来的监听功能模块对象,也为我们使用的类的入口对象,我们可以通过其主动观察对象,就如其前面提到的使用案例一样,其会随着持有者(调用观察者方法的对象)的释放而释放
参考最上面的图,FBKVOController实际上只针对一个观察者与多个被观察者形成的一对多关系
FBKVOController的成员变量如下所示:
{
//以被观察者对象为key,每一个value里面保存的是其对应的被观察者的键值回调基本载体(_FBKVOInfo)集合
//观察者会观察多个对象(被观察者),因此会有多个key
//_objectInfosMap里面保存着观察者所观察的所有对象(被观察者)的基本载体集合
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap; //对于NSMapTable可以参考前面的文章这里的使用和字典一样
pthread_mutex_t _lock; //
}
系统为我们提供的释放方法,以观察回调为block的为例
观察者持有FBKVOController对象,以便于其跟随观察者的释放而释放
self.KVOController = = [FBKVOController controllerWithObserver:self];
//添加监听
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {
}];
就以上面的block回调方法为例(sel其实逻辑一样),我们看看其内部做了什么
- (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;
}
// 创建_FBKVOInfo,其保存这对应关系的keyPath和回调方法
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// 开启观察
[self _observe:object info:info];
}
上面的方法调用完毕后调用了_observe:info方法,其仍然为FBKVOController的对象方法,其内部检查了被观察者基本载体是否已经存在了(即:被观察者对应的键值是否已经被该对象监听),代码如下
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
// lock 加锁同步锁,避免多线程读写时造成数据读写错误
pthread_mutex_lock(&_lock);
//_objectInfosMap保存在以对象为key的的set对象,set里面包含着对该被监听者对应的键值回调基本载体(_FBKVOInfo)的集合
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// check for info existence
//检查被观察对象所对应的键值回调基本载体是否已经存在(该键值是已经被该对象所观察)
//由于重写hash等方法为对应keyPath,keyPath相同的在set中则认为相同
_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]; //保存该基本载体到该对象对象的set中
// unlock prior to callout
pthread_mutex_unlock(&_lock);
//通过_FBKVOSharedController调用系统的方法添加实际监听
[[_FBKVOSharedController sharedController] observe:object info:info];
}
注:通过上面也可以看到,新的 _FBKVOInfo 会覆盖掉老的,他们通过 keyPath作为判断条件,由于通过不同对象 object来控制一组 info 的 set,因此当在一个tableViewCell复用过程中,如果被观察者同一个 object的话(例如:controller),则只有最新一组能够正常响应,因此,可以使用model作为被观察者 object,来解决这个问题(如果是cell,则只有cell显示的能响应变化,根据情况而来即可)
介绍到这里这里引出了_FBKVOSharedController,我们先不查看怎么移除的,来看看_FBKVOSharedController他是干啥的
_FBKVOSharedController与添加监听
_FBKVOSharedController为一个单例对象,其通过NSHashMap对象保存这所有的基本载体(观察者与被观察者行程的多对多的基本载体集合),可以参考最上面的图
注意:其为所有FBKVOSharedController对象的代理,FBKVOSharedController通过该单例对象来调用系统观察方法来实现监听和移除监听
_FBKVOSharedController对象如下所示
{
//保存所有基本载体的哈希表,注意其设置为NSPointerFunctionsWeakMemory属性,
//当里面_FBKVOInfo单体对象被销毁时,将会被移除该hashTable
NSHashTable<_FBKVOInfo *> *_infos;
pthread_mutex_t _mutex;
}
//单例对象
+ (instancetype)sharedController
{
static _FBKVOSharedController *_controller = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_controller = [[_FBKVOSharedController alloc] init];
});
return _controller;
}
通过上面的FBKVOController的添加监听方法走到了_FBKVOSharedController的实际监听方法,其调用了系统的监听对象,也就意味着所有的观察实际上都是在这个类中添加和回调的
添加监听方法:
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
if (nil == info) {
return;
}
// register info,将info添加到hashTable中,以便以回调使用
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// add observer
//调用系统方法添加监听,回调对象为_FBKVOSharedController单例对象
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
//调整info的状态,并且当info不在被观察是及时移除
//特殊场景下,给定枚举值为NSKeyValueObservingOptionInitial, 并且在回调中取消注册则可能发生安全问题
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];
}
}
系统的默认监听回调
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context
{
NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
//检查对应info是否还存在,如果不存在结束
_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;
//查看KVOController即调用者持有的观察对象是否存在,不存在则意味着已经销毁结束
if (nil != controller) {
// take strong reference to observer
id observer = controller.observer;
//观察者还存在的的情况回调block和action
if (nil != observer) {
// dispatch custom block or action, fall back to default action
if (info->_block) {
NSDictionary<NSKeyValueChangeKey, 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];
}
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];
}
}
}
}
}
移除监听
前面已经介绍到,FBKVOController会随着调用者的销毁而销毁,因此当其销毁时,会调用其dealloc方法,需要主动在里面移除该对象对被观察的所有监听
FBKVOController的dealloc方法实际调用了_unobserveAll方法来移除所有_FBKVOInfo信息
注意:当_FBKVOInfo被销毁时,_FBKVOController里面对应的_FBKVOInfo也会被移除
由于_FBKVOController实际上为FBKVOController的代理监听对象,因此添加和监听的移除也会在这个对象里面进行
- (void)_unobserveAll
{
// lock
pthread_mutex_lock(&_lock);
//由于移除后里面的对象将会被销毁,所以这里系统copy了一份,为了移除监听做准备
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
// clear table and map
//移除_objectInfosMap所有的基本载体,与此同时_FBKVOSharedController中对应的载体也被移除
[_objectInfosMap removeAllObjects];
// unlock
pthread_mutex_unlock(&_lock);
_FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
//在_FBKVOSharedController里面,根据监听对象移除所有的监听
for (id object in objectInfoMaps) {
// unobserve each registered object and infos
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
}
_FBKVOSharedController里面的移除监听,为了避免其他问题,其主动移除所有的基本载体,并且将载体的观察状态调整,避免多线程产生的安全问题,实现方法如下所示
- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos
{
if (0 == infos.count) {
return;
}
// unregister info
//移除所有基本载体,避免回调问题
pthread_mutex_lock(&_mutex);
for (_FBKVOInfo *info in infos) {
[_infos removeObject:info];
}
pthread_mutex_unlock(&_mutex);
// remove observer
//调整监听状态,避免多线程产生的安全问题
for (_FBKVOInfo *info in infos) {
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}
}
到这里其就已经介绍的差不多了,其他的方法基本上都是依照该方法编写的,这里就不多介绍了
总结
1.当使用某个类添加某个功能是,并且在该类销毁时需要主动移除这个功能带来的副作用是,可以把功能独立出来,作为属性,充分利用生命周期来解决问题
2.NSHashTable和NSMapTable的合理使用,能避免许多问题
3.充分理解功能模块中产生的逻辑关系链(观察者与被观察者多对多的关系,一个观察者与多个被观察者一对多的关系),可能封装出使用更广泛,更加优秀的API
4.这里锁的使用是为了保证公共数据读写的安全问题(避免读取出错误信息),不必要的功能不必加锁