自定义KVO(四)
四、KVOController
上面Hook系统kvo相关方法的方式侵入太严重了,我们要做的其实只是需要对自己的调用负责而已,可以通过中间类来完成。这块有很多第三方框架,其中Facebook提供的KVOController是很优秀的一个框架。在这篇文章中将对这个库进行简单分析。
4.1 KVOController 的使用
#import <KVOController/KVOController.h>
- (void)viewDidLoad {
[super viewDidLoad];
self.KVOController = [FBKVOController controllerWithObserver:self];
[self.KVOController observe:self.obj keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"change:%@",change);
}];
[self.KVOController observe:self.obj keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"change:%@",change);
}];
[self.KVOController observe:self.obj keyPath:@"nickName" options:NSKeyValueObservingOptionNew action:@selector(hp_NickNameChange:object:)];
}
- (void)hp_NickNameChange:(NSDictionary *)change object:(id)object {
NSLog(@"change:%@ object:%@",change,object);
}输出:
change:{
FBKVONotificationKeyPathKey = name;
kind = 1;
new = HP111;
}
change:{
kind = 1;
new = cat111;
} object:<HPObject: 0x6000022c91d0>vc持有FBKVOController实例KVOController。在NSObject+FBKVOController.h的关联属性。- 通过
FBKVOController实例进行注册。注册方式提供了多种。 - 对于重复添加会进行判断直接返回。
- 会自动进行移除操作。
4.2 KVOController 实现分析
KVOController主要是使用了中介者模式,官方kvo使用麻烦的点在于使用需要三部曲。KVOController核心就是将三部曲进行了底层封装,上层只需要关心业务逻辑。
FBKVOController会进行注册、移除以及回调的处理(回调包括block、action以及兼容系统的observe回调)。是对外暴露的交互类。使用FBKVOController分为两步:
- 使用
controllerWithObserver初始化FBKVOController实例。 - 使用
observe:进行注册。
4.2.1 FBKVOController 初始化
controllerWithObservercontrollerWithObserver最终会调用到initWithObserver中:
- (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;
}_observer是观察者,FBKVOController的属性。
@property (nullable, nonatomic, weak, readonly) id observer;weak类型,因为FBKVOController本身被观察者持有了。
_objectInfosMap根据retainObserved进行NSMapTable内存管理初始化配置,FBKVOController的成员变量。其中保存的是一个被观察者对应多个_FBKVOInfo(也就是被观察对象对应多个keyPath):
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;这里_FBKVOInfo是放在NSMutableSet中的,说明是去重的。
4.2.2 FBKVOController 注册
由于各个observe方式的原理差不多,这里只分析block的形式。
- (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];
}- 首先一些条件容错判断。
- 构造
_FBKVOInfo。保存FBKVOController、keyPath、options以及block。 - 调用
_observe:(id)object info:(_FBKVOInfo *)info
4.2.2.1 _FBKVOInfo
@implementation _FBKVOInfo
{
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}_FBKVOInfo中保存了相关数据信息。
并且重写了isEqual与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相同就认为是同一对象。
4.2.2.2 _observe: info:
- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
//从TableMap中获取 object(被观察者) 对应的 set
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// check for info existence
//判断对应的keypath info 是否存在
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
//存在直接返回,这里就相当于对于同一个观察者排除了相同的keypath
// observation info already exists; do not observe it again
// unlock and return
pthread_mutex_unlock(&_lock);
return;
}
// lazilly create set of infos
//TableMap数据为空进行创建设置
if (nil == infos) {
infos = [NSMutableSet set];
//<被观察者 - keypaths info>
[_objectInfosMap setObject:infos forKey:object];
}
// add info and oberve
//keypaths info添加 keypath info
[infos addObject:info];
// unlock prior to callout
pthread_mutex_unlock(&_lock);
//注册
[[_FBKVOSharedController sharedController] observe:object info:info];
}- 首先判断
kayPath是否已经被注册了,注册了直接返回,这里也就进行了去重处理。 - 将构造的
_FBKVOInfo信息添加进_objectInfosMap中。 - 调用
_FBKVOSharedController进行真正的注册。
member:说明member会调用到_FBKVOInfo中的hash以及isEqual进行判断对象是否存在,也就是判断keyPath对应的对象是否存在。





+ (NSUInteger)hash {
return _objc_rootHash(self);
}
- (NSUInteger)hash {
return _objc_rootHash(self);
}
+ (BOOL)isEqual:(id)obj {
return obj == (id)self;
}
- (BOOL)isEqual:(id)obj {
return obj == self;
}
uintptr_t
_objc_rootHash(id obj)
{
return (uintptr_t)obj;
}hash默认实现将对象地址转换为uintptr_t类型返回。isEqual:直接判断地址是否相同。member:根据汇编可以看到大概逻辑是先计算参数的hash,然后集合中的元素调用isEqual参数是hash值。
4.2.2.3 _unobserve:info:
- (void)_unobserve:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
// get observation infos
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// lookup registered info instance
_FBKVOInfo *registeredInfo = [infos member:info];
if (nil != registeredInfo) {
[infos removeObject:registeredInfo];
// remove no longer used infos
if (0 == infos.count) {
[_objectInfosMap removeObjectForKey:object];
}
}
// unlock
pthread_mutex_unlock(&_lock);
// unobserve
[[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
}
- (void)_unobserve:(id)object
{
// lock
pthread_mutex_lock(&_lock);
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// remove infos
[_objectInfosMap removeObjectForKey:object];
// unlock
pthread_mutex_unlock(&_lock);
// unobserve
[[_FBKVOSharedController sharedController] unobserve:object infos:infos];
}
- (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];
}
}_unobserve提供了3个方法进行移除。分别对应keyPath、observerd(被观察对象)、observer(观察者)。- 最终都是通过
_FBKVOSharedController的unobserve进行移除。
4.2.3 _FBKVOSharedController
[[_FBKVOSharedController sharedController] observe:object info:info];4.2.3.1 sharedController
_FBKVOSharedController是个单例,有成员变量_infos:
NSHashTable<_FBKVOInfo *> *_infos;FBKVOController为单例是因为它被观察者持有,它是单例观察者就无法释放了。这里_infos存储的是所有类的_FBKVOInfo信息- (instancetype)init
{
self = [super init];
if (nil != self) {
NSHashTable *infos = [NSHashTable alloc];
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
_infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
if ([NSHashTable respondsToSelector:@selector(weakObjectsHashTable)]) {
_infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
} else {
// silence deprecated warnings
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_infos = [infos initWithOptions:NSPointerFunctionsZeroingWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
#pragma clang diagnostic pop
}
#endif
pthread_mutex_init(&_mutex, NULL);
}
return self;
}infos的初始化是weak的,也就是它不影响_FBKVOInfo的引用计数。
4.2.3.2 observe: info:
- (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
//被观察者调用官方kvo进行注册,context 传递的是 _FBKVOInfo 信息。
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
//状态变为Observing
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];
}
}- 首先自己持有了传进来的
info信息。 observe: info:中调用系统kvo方法观察注册。context传递的是_FBKVOInfo信息。- 对于系统而言观察者是
_FBKVOSharedController。
4.2.3.3 unobserve: info:
- (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;
}- 调用系统的
removeObserver移除观察。
4.2.3.4 observeValueForKeyPath
既然是在4.2.3_FBKVOSharedController中进行的注册,那么系统的回调observeValueForKeyPath必然由它实现:
- (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) {
//将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];
}
}
}
}
}- 从
info中获取观察者,info信息是context传递过来的。 - 在
_FBKVOInfo存在的情况下根据类型(block、action、系统原始回调)进行了回调。block回调的过程中添加了keyPath。
4.2.4 自动移除观察者
在FBKVOController的dealloc中调用了unobserveAll进行移除:
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}由于FBKVOController的实例是被观察者持有的,所以当观察者dealloc的时候FBKVOController实例也就dealloc了。在这里调用就相当于在观察者dealloc中调用了移除。
FBKVOController流程

五、通过gnustep探索
kvo与kvc相关的代码苹果并没有开源,对于它们的探索可以通过gnustep查看原理,gnustep中有一些苹果早期底层的实现。

5.1 addObserver

setup()中是对一些表的初始化。replacementForClass创建并注册kvo类。- 创建
GSKVOInfo信息加入Map中。然后进行isa替换。 - 重写
setter方法。








- 根据是否开启自动回调决定是否调用
willChangeValueForKey以及didChangeValueForKey。
didChangeValueForKey

notifyForKey发送通知。notifyForKey:ofInstance:prior:


