如果说书籍是人类进步的阶梯,那么优秀的开源代码就是程序员提升的桥梁。研读源码可以学习其中的框架和模式, 代码技巧, 算法等,然后不断总结运用,最终这些会变成自己的东西,编程水平自然也提高了。
1、FBKVOController优势
FBKVOController
是Facebook开源的接口设计优雅的KVO框架,线程安全的KVO ,它提供block解决了以前使用KVO时代码散乱的缺点,方便实用,github地址。
- 不需要手动移除观察者;当FBKVOController对象被释放时, 观察者被隐式移除
- 实现 KVO 与事件发生处的代码上下文相同,不需要跨方法传参数;依旧不懂
- 使用 block 来替代方法能够减少使用的复杂度,提升使用 KVO 的体验;block或者selector的方式,方便使用
- 每一个 keyPath 会对应一个属性,不需要在 block 中使用 if 判断 keyPath;一个keyPath对应一个SEL或者block,不需要统一的observeValueForKeyPath方法里写if判断
- 可以同时对一个对象的多个属性进行监听,写法简洁
2、FBKVOController的类介绍
FBKVOController
的核心代码一共4个类:
- NSObject+FBKVOController.h
- NSObject+FBKVOController.m
- FBKVOController.h
- FBKVOController.m
NSObject+FBKVOController.m
是一个Category。通过AssociateObject给NSObject提供一个KVOController
和KVOControllerNonRetaining
的KVOController属性。
FBKVOController.m一共可以分为3部分:
- _FBKVOInfo
- _FBKVOSharedController
- FBKVOController
- 其中
_FBKVOInfo
主要是对需要观测的信息的包装,没有任何业务逻辑,只是一个简单的Model,主要是将以下的实例变量封装到对象中,包含了action、block、options等等,改类中重写了hash,isEqual等方法,用来判断是否两个Info是否一致。(后续会有用)
@implementation _FBKVOInfo
{
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}
-
FBKVOController
是核心类,包含MapTable
和pthread_mutex_lock
,其中_objectInfosMap
是存储一个对象对应的KVOInfo的映射关系,也就是说这里
中的id就是对象,*>
MutableSet就是KVOInfos,各种键值观测的包装。
@interface FBKVOController : NSObject
#pragma mark - Initialize 初始化方法
+ (instancetype)controllerWithObserver:(nullable id)observer;
//参数:observer是观察者,retainObserved:表示是否强引用被观察的对象,这是一个全能初始化的对象方法,其他初始化方法内部均调用该方法
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;
//该初始化方法内部调用上一个初始化方法,默认强引用被观察的对象
- (instancetype)initWithObserver:(nullable id)observer;
#pragma mark - Observe 监听方法
- (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;
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//监听多个属性的方法
- (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;
- (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options action:(SEL)action;
- (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
@implementation FBKVOController
{
NSMapTable *> *_objectInfosMap;
pthread_mutex_t _lock;
}
...//此处省略源码,可以下载源码自己去看
@end
-
FBKVOSharedController
是一个实际操作类,负责将FBKVOController
发送过来的信息转发给系统的KVO处理。
@implementation _FBKVOSharedController
{
NSHashTable<_FBKVOInfo *> *_infos;
pthread_mutex_t _mutex;
}
4.NSObject+FBKVOController
NSObject+FBKVOController 分类比较简单,它主要通过runtime方法,以懒加载的形式给 NSObject ,创建并关联一个 FBKVOController 的对象。
@interface NSObject (FBKVOController)
@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
@end
3、键值观测的源码流程
先来看全能初始化方法内部的实现,该方法对三个实例变量_observer(观察者),_objectInfosMap(NSMapTable,被监听对象->被监听属性集合之间的映射关系),pthread_mutex_init(互斥锁):
//全能初始化方法
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
self = [super init];
if (nil != self) {
//观察者
_observer = observer;
//NSMapTable中的key可以为对象,而且可以对其中的key和value弱引用
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
//对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER
//对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy
pthread_mutex_init(&_lock, NULL);
}
return self;
}
这里请先思考以下问题:
- 属性observer为何使用weak,它和哪个对象之间会导致循环引用问题,是如何导致循环引用问题的?
- 为何不使用字典来保存被监听对象和被监听属性集合之间的关系?
- NSDictionary的局限性有哪些?NSMapTable相对字典,有哪些优点?
- 互斥锁是为了保证哪些数据的线程安全?
带着这些问题我们来看FBKVOController内部是如何实现监听的,这里我们只看带Block回调的一个监听方法,其他几个方法和这个方法内部实现是相同的。下面的方法内部做了如下工作:
- 传入的参数keyPath,block为空时,程序闪退,同时报出误提示;
- 对传入参数为空的判读;
- 利用传入的参数创建_FBKVOInfo对象;
- 调用内部私有方法实现注册监听;
#pragma mark API -
- (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];
}
该私有方法内部并没有实现真正的注册监听,这个函数- (void)_observe:(id)object info:(_FBKVOInfo *)info是FBKVOController的
,首先是加锁,防止读写干扰。然后我们查找一下这个object对应的MutableSet,如果有对应的KVOInfo的话,那么就不需要再添加入_objectInfosMap中了;如果没有,则创建info,并且加入_objectInfosMap中。最后解锁,将object传给_FBKVOSharedController处理。
内部实现思路:
- 对当前线程访问的数据_objectInfosMap进行加锁;
- 根据被监听对象object到_objectInfosMap取出被监听的属性信息对象集合infos;
- 判断被监听的属性对象info是否存在集合中;
- 如果已经存在,则不需要再次添加监听,防止多次监听;
- 如果获取的集合infos为空,则建存放_FBKVOInfo对象的集合infos,保存映射关系:object->infos;
- 将被监听的信息_FBKVOInfo对象存到集合infos中;
- 解锁,其他线程可以访问该数据;
- 调用_FBKVOSharedController 的方法实现监听
- (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];
}
这个- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
函数应该是调用系统的addObserver,添加观察者。其中[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
这句话就是系统的KVO观测,object添加观测者,这里添加的是FBKVOShare,最后KVO的相应函数也在这里,正好呼应。
_FBKVOSharedController:
- (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];
}
}
这里是KVO的相应函数,有木有很熟悉?这里将当初我们传入的一些action或者block执行以下,完美。
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary *)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 *changeWithKeyPath = change;
// add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
if (keyPath) {
NSMutableDictionary *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];
}
}
}
}
循环引用的分析
VC1持有_kvoCtrl,_kvoCtrl持有一个_objectInfosMap,这是一个可以存放弱指针的NSDictionary,这个函数[_objectInfosMap setObject:infos forKey:object];就是将object和其需要监听的info加入map中。故VC1持有KVOCtrl,KVOCtrl持有map,map持有VC2,也就是VC1持有VC2。这是要如果我们VC2里观测VC1,就会VC2持有VC1。造成循环引用。
FBKVOController是线程安全的,相对于系统的KVO而言,使用起来更方便,安全,简洁。
1.NSHashTable和NSMapTable的使用;
2.互斥锁pthread_mutex_t的使用
3.FBKVOController和Observer之间循环引用的形成和解决;
4.FBKVOController和_FBKVOInfo之间循环引用的形成和解决;
参考地址:
https://www.jianshu.com/p/77b1d627780e
https://www.jianshu.com/p/1f7d70ff2002