阅读 685

OC底层原理探索之KVO原理分析下| 8月更文挑战

KVO探索原理

通过上一篇我们知道,在添加观察之后,isa指向发生了变化,指向了动态子类NSKVONotifying_Person。该子类有4个方法

  1. setNickName:观察对象的set方法
  2. class:重写class方法
  3. dealloc:在该方法中isa指向又重新指回了父类

自定义KVO思路

  1. addObserver时确保当前的类(Person)keyPath有对应的setter方法
  2. 动态生成的子类也就是isa_siwziling,给子类添加对应的方法
  3. 把isa指向从Person指到NSKVONotifying_Person
  4. 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类 image.png

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来接收keyPathoptionblock 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的内部实现跟本篇的自定义大致思想是一致的。

文章分类
iOS
文章标签