自定义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:


