kvo实现原理

系统会生成一个中间类,让对应对象的isa指向该中间类,同时系统在该中间类覆写setter方法,达到通知的目的

那成员变量等非property怎么办?

需要自己手动处理,方式也是类似的,自己手动设置setter方法,在setter方法中手动调用 willChangeValueForKey 跟 didChangeValueForKey,同时覆写automaticallyNotifiesObserversForKey返回NO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@implement SomeClass
{
int _localValue;
}
- (void)setLocalValue:(int)localValue {
[self willChangeValueForKey:@"localValue"];
_localValue = localValue;
[self didChangeValueForKey:@"localValue"];
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
if ([theKey isEqualToString:@"localValue"]) {
return NO;
}
return YES;
}

系统kvo的问题

  1. 调用繁琐
    需要addObserver,然后覆写observeValueForKeyPath方法,在方法内部判断当前key是否是监听对象及属性。

  2. 容易造成内存泄露
    每次dealloc的时候需要手动remove

替代者:KVOController

KVOController是Facebook推出的一个基于系统kvo的库,实现简洁,并且解决了上述调用繁琐跟需要手动remove的两大问题

首先,看一下使用kvocontroller之后怎么使用kvo:

1
2
3
4
5
6
[self.KVOController observe:someObj
keyPath:@"someValue"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"%@", change);
}];

只需要一句话,使用起来非常简单,并且回调是以block的形式,也非常直观。
接下来就看一下KVOController是如何实现的

KVOController是通过category的方式给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
25
26
@interface NSObject (FBKVOController)
@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
@end
@implementation NSObject (FBKVOController)
- (FBKVOController *)KVOController
{
id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey);
// lazily create the KVOController
if (nil == controller) {
controller = [FBKVOController controllerWithObserver:self];
self.KVOController = controller;
}
return controller;
}
- (void)setKVOController:(FBKVOController *)KVOController
{
objc_setAssociatedObject(self, NSObjectKVOControllerKey, KVOController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

从字面上可以了解到有两种controller,强持有对象跟弱持有。

FBKVOController内部有两个关键成员变量:_objectInfosMap 跟 lock

1
2
3
4
5
@implementation FBKVOController
{
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
pthread_mutex_t _lock;
}

lock是操作map的锁,这个无需多言,所以我们主要关注 _objectInfosMap。

_objectInfosMap 是一个NSMapTable类型的对象。在我之前一篇讲弱引用集合的文章里提到过NSMapTable,它相对于NSDictionary的优势就是支持多引用类型。NSDictionary会强持有内部所有的元素,而NSMapTable可以通过参数来设置是强持有还是弱持有。(所以如果以后大家有类似的需求,需要自己设定集合的引用类型时,可以考虑使用NSMapTable)

_objectInfosMap的value是_FBKVOInfo类型的set。_FBKVOInfo是一个私有的类型,保存着系统kvo相关的一些信息,如keyPath,option,context等,以及一些KVOController需要的信息,如对应KVOController,回调block,状态等。具体_FBKVOInfo内的成员变量为:

1
2
3
4
5
6
7
8
9
10
11
@implementation _FBKVOInfo
{
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}

好,大致了解KVOController所使用的一些类型之后,我们来看一下主要的几个接口实现

  1. init
    KVOController初始化实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - (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;
    }

这里主要是根据是否是强引用来初始化对应的_objectInfosMap,以及初始化锁。

  1. observe
    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];
    }

可以看到对外暴露的observe接口主要是初始化info对象,然后调用内部统一的_observe方法

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];
}

内部的_observe方法主要校验是否监听过该对象,然后把当前info加入到对应对象的set列表中,然后再调用一个私有类_FBKVOSharedController的observe方法
根据接口[_FBKVOSharedController sharedController]可以判断 _FBKVOSharedController是一个单例对象。它主要作用就是跟系统kvo对接。同时_FBKVOSharedController内部保存一个全局的所有info的NSHashTable,目的是方便系统observeValueForKeyPath回调回来之后,可以方便的判断当前回调是谁的,然后进行之后的操作。_FBKVOSharedController的几个主要函数:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info
{
if (nil == info) {
return;
}
// unregister info
pthread_mutex_lock(&_mutex);
[_infos removeObject:info];
pthread_mutex_unlock(&_mutex);
// remove observer
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}
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];
}
}
}
}
}

可以看到,当系统observeValueForKeyPath回来之后,_FBKVOSharedController从infos里面取出对应info,然后调用对应info的回调通知(block,selector或者系统回调)

  1. dealloc
    这里是不需要手动在外部对象dealloc的时候进行remove操作的关键。因为整个外部没有对调用者进行强持有,调用者可以正常释放。当调用者释放的时候,也会释放掉kvocontroller,然后kvocontroller再在dealloc里面进行removeobserver的操作。
    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
    - (void)dealloc
    {
    [self unobserveAll];
    pthread_mutex_destroy(&_lock);
    }
    - (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];
    }
    }

参考:

  1. Draveness | 如何优雅地使用 KVO