kvo实现原理
系统会生成一个中间类,让对应对象的isa指向该中间类,同时系统在该中间类覆写setter方法,达到通知的目的
那成员变量等非property怎么办?
需要自己手动处理,方式也是类似的,自己手动设置setter方法,在setter方法中手动调用 willChangeValueForKey 跟 didChangeValueForKey,同时覆写automaticallyNotifiesObserversForKey返回NO:
系统kvo的问题
调用繁琐
需要addObserver,然后覆写observeValueForKeyPath方法,在方法内部判断当前key是否是监听对象及属性。容易造成内存泄露
每次dealloc的时候需要手动remove
替代者:KVOController
KVOController是Facebook推出的一个基于系统kvo的库,实现简洁,并且解决了上述调用繁琐跟需要手动remove的两大问题
首先,看一下使用kvocontroller之后怎么使用kvo:
只需要一句话,使用起来非常简单,并且回调是以block的形式,也非常直观。
接下来就看一下KVOController是如何实现的
KVOController是通过category的方式给NSObject加入一个KVOController的属性,加入方式就是通过关联对象。
从字面上可以了解到有两种controller,强持有对象跟弱持有。
FBKVOController内部有两个关键成员变量:_objectInfosMap 跟 lock
|
|
lock是操作map的锁,这个无需多言,所以我们主要关注 _objectInfosMap。
_objectInfosMap 是一个NSMapTable类型的对象。在我之前一篇讲弱引用集合的文章里提到过NSMapTable,它相对于NSDictionary的优势就是支持多引用类型。NSDictionary会强持有内部所有的元素,而NSMapTable可以通过参数来设置是强持有还是弱持有。(所以如果以后大家有类似的需求,需要自己设定集合的引用类型时,可以考虑使用NSMapTable)
_objectInfosMap的value是_FBKVOInfo类型的set。_FBKVOInfo是一个私有的类型,保存着系统kvo相关的一些信息,如keyPath,option,context等,以及一些KVOController需要的信息,如对应KVOController,回调block,状态等。具体_FBKVOInfo内的成员变量为:
好,大致了解KVOController所使用的一些类型之后,我们来看一下主要的几个接口实现
- init
KVOController初始化实现如下:1234567891011- (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,以及初始化锁。
- observe12345678910111213- (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方法
内部的_observe方法主要校验是否监听过该对象,然后把当前info加入到对应对象的set列表中,然后再调用一个私有类_FBKVOSharedController的observe方法
根据接口[_FBKVOSharedController sharedController]可以判断 _FBKVOSharedController是一个单例对象。它主要作用就是跟系统kvo对接。同时_FBKVOSharedController内部保存一个全局的所有info的NSHashTable,目的是方便系统observeValueForKeyPath回调回来之后,可以方便的判断当前回调是谁的,然后进行之后的操作。_FBKVOSharedController的几个主要函数:
|
|
|
|
可以看到,当系统observeValueForKeyPath回来之后,_FBKVOSharedController从infos里面取出对应info,然后调用对应info的回调通知(block,selector或者系统回调)
- dealloc
这里是不需要手动在外部对象dealloc的时候进行remove操作的关键。因为整个外部没有对调用者进行强持有,调用者可以正常释放。当调用者释放的时候,也会释放掉kvocontroller,然后kvocontroller再在dealloc里面进行removeobserver的操作。123456789101112131415161718192021222324252627- (void)dealloc{[self unobserveAll];pthread_mutex_destroy(&_lock);}- (void)_unobserveAll{// lockpthread_mutex_lock(&_lock);NSMapTable *objectInfoMaps = [_objectInfosMap copy];// clear table and map[_objectInfosMap removeAllObjects];// unlockpthread_mutex_unlock(&_lock);_FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];for (id object in objectInfoMaps) {// unobserve each registered object and infosNSSet *infos = [objectInfoMaps objectForKey:object];[shareController unobserve:object infos:infos];}}
参考: