KVO探索原理
通过上一篇我们知道,在添加观察之后,isa指向发生了变化,指向了动态子类NSKVONotifying_Person
。该子类有4个方法
setNickName
:观察对象的set方法class
:重写class方法dealloc
:在该方法中isa指向又重新指回了父类
自定义KVO思路
addObserver
时确保当前的类(Person)
的keyPath
有对应的setter
方法- 动态生成的子类也就是isa_siwziling,给子类添加对应的方法
- 把isa指向从Person指到
NSKVONotifying_Person
- set方法处理
自定义KVO-addObserver
1.首先从keyPath可以拼凑推导出set方法。eg:(name-> setName)。我们先判断本类中是否存在setName方法,如果不存在就报出异常。
- (void)hb_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
//1.确保存在keyPath的set方法
[self getSetterMethodFromKeyPath:keyPath];
//2.动态生成子类
Class newClass = [self creatChildClassByKeyPath:keyPath];
//3.isa指向派生的子类
object_setClass(self, newClass);
//4.保存观察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
2.动态生成子类的时候判断当前的子类是否存在,如果存在直接返回。不存在的话就申请、注册、然后动态添加方法
setName:
和class
#pragma mark - 动态生成子类
-(Class)creatChildClassByKeyPath: (NSString *)keyPath {
// 2.1获取当前的类的名字
NSString *oldClassName = NSStringFromClass([self class]);
// 获取当前的类的子类名字
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kHBKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 2.2判断是否存在
if (newClass) return newClass;
// 2.3不存在就创建 申请-注册-添加方法
objc_allocateClassPair([self class], newClassName.UTF8String, 0);
objc_registerClassPair(newClass);
// 2.3.1添加方法class方法 class指向的是Person
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)hb_class, classTypes);
//2.3.2添加setKeyPath方法
SEL setterSEL = NSSelectorFromString(setterFromKeyPath(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)hb_setter, setterTypes);
// 2.3.3 : 添加dealloc
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)hb_dealloc, deallocTypes);
return newClass;
}
自定义KVO-setter方法
在当前派生子类重写的setName:
方法中我们需要做3点
1.判断自动开关是否打开
2.发送到父类Person的set方法中(因为我们set方法最终还是修改掉了父类的set)
3.发送完成之后需要一个回调 也就是给观察者发送通知
static void hb_setter(id self, SEL _cmd, id newValue){
NSLog(@"来了:%@",newValue );
// 消息转发给父类
NSString *keyPath = getterFromKeyPath(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
void (*hb_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
hb_msgSendSuper(&superStruct, _cmd, newValue);
// 1: 拿到观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
// 2: 消息发送给观察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
自定义KVO-优化
观察的数据比较多的话,我们最好弄一个对象来保存观察者,以及keyPath的新值和旧值,所以在最开始的步骤4保存观察者的可以这么写:
typedef NS_OPTIONS(NSUInteger, HBKeyValueObservingOptions) {
HBKeyValueObservingOptionNew = 0x01,
HBKeyValueObservingOptionOld = 0x02,
};
@interface HBKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) HBKeyValueObservingOptions options;
// 4: 保存观察者信息
HBKVOInfo *info = [[HBKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
if (!observerArr) {
observerArr = [NSMutableArray arrayWithCapacity:1];
[observerArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
在setter方法中给观察者发回调信息的话就遍历
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
for (HBKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
// 对新旧值进行处理
if (info.options & HBKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & HBKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
// 2: 消息发送给观察者
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(info.observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
});
}
}
自定义KVO-removeObserver
在移除观察者的时候首先需要遍历observerArr
把observer中的keyPath给移除掉,然后把isa重新指向父类
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
自定义函数式KVO
typedef void(^HBKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
这么设计的话需要变动的地方有:
- (void)hb_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block;
@interface HBKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) HBKVOBlock handleBlock;
@end
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kHBKVOAssiociateKey));
for (HBKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
KVO自动销毁机制
希望达到的效果是不用用户调用,自动释放。我们看到上面的removeObserver
我们在这里面释放,但是如果用户不调用这个方法的话,是永远不会释放的。同时在上面的自定义的setter方法中,我们派生的子类还有一个dealloc
方法没有实现。把dealloc
的方法实现指定为下面的函数
static void hb_dealloc(id self,SEL _cmd){
Class superClass = [self class];
object_setClass(self, superClass);
}
工程中验证一下,在这个方法调用之前self.person指向的是派生类,这个之后指向的是Person类
FaceBook的FBKVOController
思考
KVO的麻烦的地方在于每次移除比较麻烦,观察和回调的代码段不是连续的比较分散,看起来写起来都比较费劲。而FBKVOController
利用了中介者模式,函数响应式调用、不需要手动销毁,vc不需要关注释放,它下层已经做好了相关的工作。
self-> _kvoCtrl -> FB单例 -> self.person
- (FBKVOController *)kvoCtrl{
if (!_kvoCtrl) {
_kvoCtrl = [FBKVOController controllerWithObserver:self];
}
return _kvoCtrl;
}
[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
1.创建一个info来接收keyPath
、option
、block
2.创建一个单例来把object
这里指的是self.person
和info关联起来
3.调用系统自身的kvo
4.在回调时拿到对应的block
然后执行
FB自动销毁 我们控制器释放的时候,_kvoCtrl也会同步释放。
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}
- (void)unobserveAll
{
[self _unobserveAll];
}
释放的时候,拿到objectInfo的哈希map表一个一个移除,同时也移除所有的对象和信息。
- (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];
}
}
GNUstep Base Library
GNU 苹果开源的代码,可以看到KVO的内部实现跟本篇的自定义大致思想是一致的。