前言
在阅读公司源码的一些功能时发现了KVOController这个神奇的库。这个库十分的好用,可以主动的去观察一个对象的属性。
例如
[self.KVOControllerNonRetaining observe:_test
keyPath:@"test"
options:0
block::^(id _Nullable observer, id object, NSDictionary<NSString *, id> *change) {
}];
KVOController的源码不多,加上分类也就不到800行,所以我花了一段时间阅读它的源码,这篇文章是阅读源码的总结。
类图
下图是我通过阅读源码画的UML类图,因为有些偷懒,所以这个类图的方法并不全。但这并不重要,这张类图的意义在于我们能够清晰地看明白他们之间的关系。
KVOController类图.png解析
_FBKVOInfo
_FBKVOInfo作为一个被两个类组合的类,在KVOController中属于Model的性质,用来保存所需要的内容,以下是这个类拥有的变量
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
_controller
_FBKVOInfo在FBKVOController中初始化,初始化时就把FBKVOController对象持有了,这里用一个weak修饰防止循环引用
_keyPath
这个应该不怎么需要解释,这个就是KVO观察的keyPath
_options
这个也是KVO观察的设置,是一个枚举,设置不同的枚举KVO效果是不同的,这里就不详细展开了。
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,
NSKeyValueObservingOptionInitial = 0x04,
NSKeyValueObservingOptionPrior = 0x08
};
_action、_block
这个是用来保存FBKVOController需要调用的方法和block
context
上下文,这个也不多解释
_state
这是一个很重要的枚举,用来保存_FBKVOInfo所对应对象的观察状态
typedef NS_ENUM(uint8_t, _FBKVOInfoState) {
// 初始化状态
_FBKVOInfoStateInitial = 0,
// 被观察状态
_FBKVOInfoStateObserving,
// 没被观察状态
_FBKVOInfoStateNotObserving,
};
FBKVOController
FBKVOController是KVOController对外暴露的类,其中我们主要用以下两个方法
- (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;
一个是KVO之后的block的回调,另一个是KVO之后调用的方法,下面我们以第一个方法进行讲解。
- (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对象
- 调用_observe:info:
根据上文的结论,我们可以得知_FBKVOInfo是一个Model的存在,所以需要先把它初始化了。
- (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];
}
_objectInfosMap是临界资源,所以在这个方法里进行加锁防止资源被争抢。
方法执行步骤:
- 加锁
- 判断是否存在,存在即解锁结束,不需要再次观察;不存在则进入步骤3
- 判断_objectInfosMap所对应的集合是否存在,存在则继续;不存在则初始化并保存在_objectInfosMap中
- 保存新的_FBKVOInfo对象
- 解锁
- 调用_FBKVOSharedController
这里涉及到一个知识点是NSMapTable,这是一个类似NSDictionary的容器,但是它不仅能做到key和value之间的映射关系,它也能做到object和object之间的映射关系。这种object和object之间的映射关系在KVOController中体现的很好,每一个被观察者(object)对应一个_FBKVOInfo对象(object)。推荐阅读NSMapTable: 不只是一个能放weak指针的 NSDictionary
_FBKVOSharedController
_FBKVOSharedController它是一个单例,这个私有类才是KVOController提供服务的实际实现类。
我们继续来看_FBKVOSharedController被FBKVOController所调用的方法
- (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) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
方法执行步骤:
- 添加临界资源
- 注册观察
- 判断_FBKVOInfo对象state,若为初始化,则改变为观察中,若为不在观察中,则移除这个观察
这里涉及到NSHashTable,这个类似于NSSet,本文对此不展开说明。
之所以说_FBKVOSharedController才是KVOSharedController的实际实现类是因为它实现了KVO的回调方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSString *, id> *)change
context:(nullable void *)context
我们来看一下里面的内容
- (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;
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];
}
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];
}
}
}
}
}
方法执行步骤
- 断言
- 通过context上下文从临界资源_infos中拿到info
- 进行保护,防止持有的FBKVOController和observe为空
- 判断_info持有的block或SEL是否存在,存在则调用;不存在则把消息转发给observe
最后一步调用发现block或者SEL都不存在时必须让object调用,因为observe里可能存在observeValueForKeyPath的实现
为什么使用FBKVOController不需要移除通知
在FBKVOController的dealloc里是这样写的
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}
unobserveAll所调用的是
- (void)_unobserveAll
{
// lock
pthread_mutex_lock(&_lock);
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
// clear table and map
[_objectInfosMap removeAllObjects];
// unlock
pthread_mutex_unlock(&_lock);
_FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
for (id object in objectInfoMaps) {
// unobserve each registered object and infos
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
}
可以发现FBKVOController通过遍历Map,把所持有的观察者都一一去除了
最终调用的方法是_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;
}
}
这个方法可以看出来object所对应的_FBKVOSharedController所持有的_FBKVOInfo全部都被removeObserver了
"NSObject+FBKVOController.h"
KVOController还有一个NSObject的分类,提供两种方式使用KVOController的懒加载,分别是持有方式和不持有方式。
- (FBKVOController *)KVOController
{
id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey);
// lazily create the KVOController
if (nil == controller) {
controller = [FBKVOController controllerWithObserver:self];
self.KVOController = controller;
}
return controller;
}
- (FBKVOController *)KVOControllerNonRetaining
{
id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey);
if (nil == controller) {
controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO];
self.KVOControllerNonRetaining = controller;
}
return controller;
}
他们的区别就是被观察者的内存管理机制是strong还是weak,前者是strong,后者是weak。
网友评论