注册

自定义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会进行注册、移除以及回调的处理(回调包括blockaction以及兼容系统的observe回调)。是对外暴露的交互类。使用FBKVOController分为两步:

  1. 使用 controllerWithObserver 初始化FBKVOController实例。
  2. 使用observe:进行注册。

4.2.1 FBKVOController 初始化

controllerWithObserver
controllerWithObserver最终会调用到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。保存FBKVOControllerkeyPathoptions以及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中保存了相关数据信息。

并且重写了isEqualhash方法:

- (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对应的对象是否存在。



3d0a04edbfc0e53bdc320f159c6250d5.png7c2bd427f371b1450c906b5061e656e8.png8bb452a80760cfad19d0044ff743644d.png




官方API说明:

12d55793bd4c127ceb1bef2809518756.png


bde87ac328442968ecaaef317364de08.png


源码实现:

+ (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个方法进行移除。分别对应keyPathobserverd(被观察对象)、observer(观察者)。
  • 最终都是通过_FBKVOSharedControllerunobserve进行移除。

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存在的情况下根据类型(blockaction、系统原始回调)进行了回调。block回调的过程中添加了keyPath

4.2.4 自动移除观察者

FBKVOControllerdealloc中调用了unobserveAll进行移除:

- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}

由于FBKVOController的实例是被观察者持有的,所以当观察者dealloc的时候FBKVOController实例也就dealloc了。在这里调用就相当于在观察者dealloc中调用了移除。

FBKVOController流程

a28b406a85b4ec717a13ef6a26f747e7.png


五、通过gnustep探索

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


db1df78819a721b37335adf36fa3c46e.png

5.1 addObserver

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



d347d219be803b622f3c15bae383547d.png87cf4230d6fa5482628f8e3c21ed9eb0.png713cea7295c4b81a413cc32b47f33318.png

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

didChangeValueForKey

2c6d723077b1b59384b8d0ce56100831.png


最终调用了notifyForKey发送通知。

notifyForKey:ofInstance:prior:

979fba93c3bd42c36eeb0f03fd63366a.png74baf7a1ee1e099c3ba1ecd125e75958.png25589d465692e7df0df66ea5e0404e62.png


0 个评论

要回复文章请先登录注册