- 源码: github.com/facebook/KV…
- 版本: v1.2.0
- 用途: 更加方便的使用 KVO
相关类
_FBKVOInfo
{
@public
__weak FBKVOController *_controller; // 防止观察者释放了然而_controller没有释放
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}
- (instancetype)initWithController:(FBKVOController *)controller
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(nullable FBKVONotificationBlock)block
action:(nullable SEL)action
context:(nullable void *)context
{
self = [super init];
if (nil != self) {
_controller = controller; // FBKVOController(包含观察者用于SEL的回调)
_block = [block copy]; // 三种回调方式中的Block
_keyPath = [keyPath copy]; // KVO的 forKeyPath
_options = options;
_action = action; // 三种回调方式中的SEL
_context = context; // 三种回调方式中自己实现observeValueForKeyPath的context
}
return self;
}
复制代码
- 相当于一个
Model
来记录相关属性 - 重写
- (NSUInteger)hash
函数, 防止在集合类型中添加重复的_keyPath
- 重写
- (BOOL)isEqual:(id)object
进行对象的判等
_FBKVOSharedController
- 事件的逻辑处理
// 看似很长其实也不多
- (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;
// 由于 _FBKVOSharedController 是单例 不会释放,观察者可能释放掉了进而释放了FBKVOController
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];
}
//第一种处理方式 Block 回调
info->_block(observer, object, changeWithKeyPath);
} else if (info->_action) {
// 写给编译器看的 去除"-Warc-performSelector-leaks"警告
// 参考 https://nshipster.cn/pragma/
#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];
}
}
}
}
}
复制代码
FBKVOController
- 接口类
- 做一些校验类工作
实现流程
- 为
NSObject
添加关联属性KVOController
(会对所观察的Object
做Retain
操作),KVOControllerNonRetaining
(不会对所观察的Object
做Retain
操作) - 通过
FBKVOController
接口类, 做一些参数校验,并生成_FBKVOInfo
添加到_FBKVOSharedController
这个单例当中 _FBKVOSharedController
实现- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
并根据context
找到相关回调并执行
细节学习
这里用了 copy
- (instancetype)initWithController:(FBKVOController *)controller
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(nullable FBKVONotificationBlock)block
action:(nullable SEL)action
context:(nullable void *)context
{
self = [super init];
if (nil != self) {
_controller = controller;
_block = [block copy];
_keyPath = [keyPath copy]; //防止传入的对象是 NSMutableString
_options = options;
_action = action;
_context = context;
}
return self;
}
复制代码
KVOController 不属于 UI
控件, 涉及多线程所以需要保证线程安全, 对在多线程访问的变量加锁
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
// 加锁
pthread_mutex_lock(&_lock);
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 检查是否添加过这个 keyPath, 防止重复添加
_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;
}
// 懒加载
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];
}
复制代码
更多锁参考 iOS 常见知识点(三):Lock
pthread_mutex_t
线程锁不属于 UIKit
和 Foundation
对象生命周期需要手动管理
- (void)dealloc {
pthread_mutex_destroy(&_mutex);
}
复制代码
NSMapTable & NSHashTable 对容器内的属性可以不增加引用计数
使用方法, 参考https://nshipster.cn/nshashtable-and-nsmaptable/