KVOController源码全解析

前言

在阅读公司源码的一些功能时发现了KVOController这个神奇的库。这个库十分的好用,可以主动的去观察一个对象的属性。

例如

1
2
3
4
5
6
[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的性质,用来保存所需要的内容,以下是这个类拥有的变量

1
2
3
4
5
6
7
__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效果是不同的,这里就不详细展开了。

1
2
3
4
5
6
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,
NSKeyValueObservingOptionInitial = 0x04,
NSKeyValueObservingOptionPrior = 0x08
};

_action、_block

这个是用来保存FBKVOController需要调用的方法和block

context

上下文,这个也不多解释

_state

这是一个很重要的枚举,用来保存_FBKVOInfo所对应对象的观察状态

1
2
3
4
5
6
7
8
typedef NS_ENUM(uint8_t, _FBKVOInfoState) {
// 初始化状态
_FBKVOInfoStateInitial = 0,
// 被观察状态
_FBKVOInfoStateObserving,
// 没被观察状态
_FBKVOInfoStateNotObserving,
};

FBKVOController

FBKVOController是KVOController对外暴露的类,其中我们主要用以下两个方法

1
2
3
- (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之后调用的方法,下面我们以第一个方法进行讲解。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (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];
}

方法执行步骤:

  1. 断言以及错误判断
  2. 创建一个_FBKVOInfo对象
  3. 调用_observe:info:

根据上文的结论,我们可以得知_FBKVOInfo是一个Model的存在,所以需要先把它初始化了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (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是临界资源,所以在这个方法里进行加锁防止资源被争抢。

方法执行步骤:

  1. 加锁
  2. 判断是否存在,存在即解锁结束,不需要再次观察;不存在则进入步骤3
  3. 判断_objectInfosMap所对应的集合是否存在,存在则继续;不存在则初始化并保存在_objectInfosMap中
  4. 保存新的_FBKVOInfo对象
  5. 解锁
  6. 调用_FBKVOSharedController

这里涉及到一个知识点是NSMapTable,这是一个类似NSDictionary的容器,但是它不仅能做到key和value之间的映射关系,它也能做到object和object之间的映射关系。这种object和object之间的映射关系在KVOController中体现的很好,每一个被观察者(object)对应一个_FBKVOInfo对象(object)。推荐阅读NSMapTable: 不只是一个能放weak指针的 NSDictionary

_FBKVOSharedController

_FBKVOSharedController它是一个单例,这个私有类才是KVOController提供服务的实际实现类。

我们继续来看_FBKVOSharedController被FBKVOController所调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (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];
}
}

方法执行步骤:

  1. 添加临界资源
  2. 注册观察
  3. 判断_FBKVOInfo对象state,若为初始化,则改变为观察中,若为不在观察中,则移除这个观察

这里涉及到NSHashTable,这个类似于NSSet,本文对此不展开说明。

之所以说_FBKVOSharedController才是KVOSharedController的实际实现类是因为它实现了KVO的回调方法

1
2
3
4
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSString *, id> *)change
context:(nullable void *)context

我们来看一下里面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- (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];
}
}
}
}
}

方法执行步骤

  1. 断言
  2. 通过context上下文从临界资源_infos中拿到info
  3. 进行保护,防止持有的FBKVOController和observe为空
  4. 判断_info持有的block或SEL是否存在,存在则调用;不存在则把消息转发给observe

最后一步调用发现block或者SEL都不存在时必须让object调用,因为observe里可能存在observeValueForKeyPath的实现

为什么使用FBKVOController不需要移除通知

在FBKVOController的dealloc里是这样写的

1
2
3
4
5
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}

unobserveAll所调用的是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (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的取消观察方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (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的懒加载,分别是持有方式和不持有方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (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。