IOS - FBKVOController框架分析
前言
一、FBKVOController的基本使用
二、整体源码结构
三、_FBKVOInfo
四、FBKVOController的构造方法和成员变量
五. 添加被观察者
六. 自动移除监听
七、简易流程图
### 经过对KVO底层的探索,基本掌握了KVO的实现原理。但是在尝试自定义KVO的过程中,还是遇到了不少问题,比如:“在动态子类中重写Setter方法的时候,需要判断所有的参数类型”以及“需要处理多线程的情况。”还需要实现手动监听功能等等。所以要想自定义一个完整的KVO,并没有那么简单。这里可以参考gnustep开源的代码: [点击下载](http://www.gnustep.org/resources/downloads.php)。换一种思路,如果不去自定义KVO,而是去封装系统的KVO,从而更加方便的使用KVO呢?那么本文就分析Facebook的一个框架:FBKVOController,看它是如何“优化KVO的使用”的。一、FBKVOController的基本使用
#cocoapods 依赖
pod 'KVOController', '~> 1.2.0'
源码地址
1、相对系统KVO的优势
(1)支持block和action的回调方式。
(2)不用手动去移除观察者
2、角色转换
系统的KVO是被观察者去添加观察者。KVOController可以主动的去添加被观察者。
3、简单的例子
@property (nonatomic, strong) FBKVOController *fbKvo;
.....
self.fbKvo = [[FBKVOController alloc] initWithObserver:self];
[self.fbKvo observe:self.viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
}];
以上就是只用FBKVOController的一个例子,监听了viewModel中的name属性。
4、同时支持Action的形式
[self.KVOController observe:self.viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew action:@selector(observeChange:object:)];
.....
- (void)observeChange:(NSDictionary *)dic object:(id)object {
}
当然KVOController 也支持系统原本的回调。
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
优先级为: block > action > 系统回调
5、通过分类直接获取KVOController对象.
//以上代码我们可以改为:
[self.KVOController observe:self.viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
}];
直接通过分类的关联属性获取KVOController。
@interface NSObject (FBKVOController)
@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
@end
这里KVOController会强持有被观察者。KVOControllerNonRetaining会弱引用被观察者,比如被观察者是观察者本身的时候,需要使用KVOControllerNonRetaining来解除循环引用。
6、不需要手动移除监听
内部会自动移除监听。
二、整体源码结构

其中FBKVOController.m中包含3个类
(1)FBKVOController
(2)_FBKVOInfo
(3)_FBKVOSharedController
三、_FBKVOInfo
先看_FBKVOInfo类:
@implementation _FBKVOInfo
{
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}
有7个成员变量:_controller,_keyPath,_options,_action,_context,_block,_state。
其中_keyPath(监听的属性名称)、_options(策略)、_action(事件)、_block(回调) 这四个成员变量很好理解,action和block是在keyPath对应的值改变的时候触发响应回调的,_options和系统KVO中的一样,是监听值的策略。
_state就是记录监听状态,是一种容错机制。
而对于_controller、_context 这3个成员变量。有2个问题:
(1)_FBKVOInfo保存这2个成员的目的是什么呢?
(2)为什么_controller是弱引用?
先插个眼,继续往下分析。
四、FBKVOController的构造方法和成员变量
1、构造方法
@interface FBKVOController : NSObject
+ (instancetype)controllerWithObserver:(nullable id)observer;
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObserver:(nullable id)observer;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
..........................................
@end
@implementation FBKVOController
{
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
pthread_mutex_t _lock;
}
构造方法中有两个宏定义:
NS_UNAVAILABLE 和 NS_DESIGNATED_INITIALIZER
NS_UNAVAILABLE 是告诉编译器此方法不可用,代码中如果调用此方法,会编译报错。(通过runtime依然可以调用)
NS_DESIGNATED_INITIALIZER 是告诉编译器,指定默认的初始化方法。
通过这两个宏,告诉我们,observer是必须在初始化FBKVOController时传进来的。
/**
The observer notified on key-value change. Specified on initialization.
*/
@property (nullable, nonatomic, weak, readonly) id observer;
此observer是弱引用,原因如下面代码:
self.KVOController = [[FBKVOController alloc] initWithObserver:self];
(->表示持有关系) self -> KVOController -> self。如果observer不是弱引用,就会循环引用。
2、成员变量:
@implementation FBKVOController
{
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
pthread_mutex_t _lock;
}
_objectInfosMap是存储被观察对象和KVOInfo的映射关系。也就是一个被观察对象对应多个_FBKVOInfo。
_objectInfosMap是NSMapTable类型。
NSMapTable与NSDictionary/NSMutableDictionary 简单做一个对比:
NSDictionary/NSMutableDictionary 中对于key和value的内存管理方法唯一,即对key进行copy,对value进行强引用,而NSMapTable有key和value的内存管理策略。
- (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;
}
通过初始化方法,可以看到,根据retainObserved来判断对key是使用weak还是strong。
另外一个成员变量_lock,是一个互斥锁。
五. 添加被观察者
1、调用方法
//这里以其中一个方法为例
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
..................
// create info
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// observe object with info
[self _observe:object info:info];
}
创建一个_FBKVOInfo对象,然后执行 [self _observe:object info:info];。
添加被观察者的核心方法:
- (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];
}
3、主要流程
(1)为了防止多线程可能造成数据混乱的问题,加了互斥锁。
(2)从_objectInfosMap中取出object对应的infos,判断info在infos中是否存在。
“这里可以确定_objectInfosMap的key是被观察者,value是info的集合。
也就是一个被观察者对应多个info。”
了解了_objectInfosMap的结构之后,总结回答2个问题:
<1>上面有提到“被观察者是观察者本身的时候,需要使用KVOControllerNonRetaining来解除循环引用”。
比如:
[self.KVOController observe:self keyPath:@"name" options:NSKeyValueObservingOptionNew
block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
}];
由于: (->表示持有关系) self -> KVOController -> _objectInfosMap -> self 造成了循环引用。所以此时应该使用:
[self.KVOControllerNonRetaining observe:self keyPath:@"naem" options:NSKeyValueObservingOptionNew
block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
}];
<2> 为什么_FBKVOInfo的成员_controller是弱引用?
因为infos被_objectInfosMap强持有,如果不弱引用_controller,就会造成:
(->表示持有关系) self -> KVOController -> _objectInfosMap -> infos -> info -> _controller(KVOController)。
最终会造成KVOController无法释放,会影响到移除观察者。
(3)判断info是否存在
根据_FBKVOInfo中重写的- (BOOL)isEqual:(id)object 和 - (NSUInteger)hash 两个方法:
- (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];
}
先对比的对象地址是否相同,最后再对比keyPath。
如果info存在,直接返回,防止重复观察。
如果info不存在,则添加到集合中。
(4)最后将被观察者(object)和 info传给_FBKVOSharedController 这个单利。
4、 _FBKVOSharedController 成员
@implementation _FBKVOSharedController
{
NSHashTable<_FBKVOInfo *> *_infos;
pthread_mutex_t _mutex;
}
_FBKVOSharedController这个单利中,也有两个成员变量:_infos 和 _mutex。
_mutex就不解释了,互斥锁,处理多线程问题。
_infos是一个NSHashTable的结构,这是因为NSHashTable和NSMapTable一样,同样支持value的内存管理策略设置。
5、 _FBKVOSharedController添加被观察者
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
........
// 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];
}
}
核心代码如下:
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
非常骚的有2点:(1)将当前的单利做为观察者。(2)将context设置为info。
- (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);
//判断对象是否再_infos中存在,如果存在则返回,不存在返回nil
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];
}
}
}
}
}
这个段代码正是系统KVO的回调方法,在此回调中,通过context从infos中判断获取_FBKVOInfo(真的骚)。
然后通过info获取controller,再通过controller获取observer。拿到observer之后,就可以把action以及系统本身的observeValueForKeyPath方法回调,回调给上层了。
分析到这里也就知道了为什么_FBKVOInfo要保存controller和context两个成员变量了。
六. 自动移除监听
因为FBKVOController 是一个实例,当控制器(self)销毁的时候,它也会被销毁。此时会走它的dealloc方法:
- (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];
}
}
遍历_objectInfosMap,拿到每一个被观察者对应的info集合,然后交给shareController移除监听。
- (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;
}
}
七、简易流程图
