KVO的自定义

一.探索前需知

1.1 KVO的底层原理实现?

  • 一、当对对象A进行KVO观察时候,会动态生成一个子类,而后将对象的isa指向新生成的子类
  • 二、KVO本质上是监听属性的setter方法,只要被观察对象有成员变量和对应的set方法,就能对该对象经过KVO进行观察
  • 三、子类会重写父类的set、class、dealloc、_isKVOA方法
  • 四、当观察对象移除全部的监听后,会将观察对象的isa指向原来的类
  • 五、当观察对象的监听所有移除后,动态生成的类不会注销,而是留在下次观察时候再使用,避免反复建立中间子类
附上篇关于KVO原理分析的地址: juejin.im/post/5e4cf4…

 二. 自定义KVO的初探

2.1 KVO自定义准备

咱们先看下系统的KVO的实现:ios

@interface NSObject(NSKeyValueObserverRegistration)

/* Register or deregister as an observer of the value at a key path relative to the receiver. The options determine what is included in observer notifications and when they're sent, as described above, and the context is passed in observer notifications as described above. You should use -removeObserver:forKeyPath:context: instead of -removeObserver:forKeyPath: whenever possible because it allows you to more precisely specify your intent. When the same observer is registered for the same key path multiple times, but with different context pointers each time, -removeObserver:forKeyPath: has to guess at the context pointer when deciding what exactly to remove, and it can guess wrong. */ - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0)); - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; @end 复制代码

原来KVO的实现是在NSObjectNSKeyValueObserverRegistration 分类里.macos

因此咱们也实现个分类,在分类里处理KVO的相关事宜:编程


接着咱们根据KVO的底层原理实现,开始一步步的自定义.api

2.2  动态生成一个子类,而后将对象的isa指向新生成的子类

从原理得知当对对象A进行KVO观察时候,会动态生成一个子类,而后将对象的isa指向新生成的子类.因此应该在这方法里:
安全

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    
    // 1: 验证是否存在setter方法 : 不让实例进来
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa的指向 : LGKVONotifying_LGPerson
    object_setClass(self, newClass);
    // 4: 保存观察者
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
复制代码

 Class newClass = [self createChildClassWithKeyPath:keyPath]; 动态生成子类时,咱们也要给新生成的子类重写父类的set、class、dealloc、_isKVOA方法.bash

#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 防止重复建立生成新类
    if (newClass) return newClass;
    /**
     * 若是内存不存在,建立生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */
    // 2.1 : 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 : 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 : 添加class : class的指向是LGPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
    // 2.3.2 : 添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
    return newClass;
}
复制代码

重写class方法指向派生类父类async

Class lg_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
复制代码

重写setter方法:函数式编程

static void lg_setter(id self,SEL _cmd,id newValue){

}
复制代码

2.3 重写派生子类的set方法

在这里你们和我一块儿思考下,系统的KVO是怎么触发的?以前文章就已经说过要想了解KVO就必须先了解KVC,KVC的调用过程就会自动触发键值观察(KVO).好比:函数

[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
self.person.nickName = @"KC"; 
复制代码

其本质是Person类底层调用了- (void)setNickName:(NSString *)nickName方法, post

这样就会触发KVO键值观察.会进行KVO的回调

#pragma mark - KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}
复制代码

因此在重写Person类的派生子类setter方法时, 要让Person也响应setter方法,再一个就是observer (观察者)响应 自定义方法 - (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object。

因此在子类setter方法里:转发消息给父类,让父类也实现setter方法,而后再给observer发送消息,响应

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    // 4: 消息转发 : 转发给父类
    // 改变父类的值 --- 能够强制类型转换
    
    void (*lg_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)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
    
    
    // 既然观察到了,下一步不就是回调 -- 让咱们的观察者调用
    // - (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    // 1: 拿到观察者
    id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    // 2: 消息发送给观察者
    SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
    
}复制代码

2.4 当观察对象移除全部的监听后,会将观察对象的isa指向原来的类

- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    // 指回给父类
    Class superClass = [self class];
    object_setClass(self, superClass);
}复制代码

到这里为止自定义KVO的基本功能就完成了.咱们来试验下 :


看运行输出:


是否是来了,说明咱们自定义的KVO已经成功了,但确定还有许多东西要优化,请接着往下面看.

三. 自定义KVO的进阶(优化)

3.1 KVO里的Observer用模型进行包装,使代码更具备面向对象的特色.

@interface LGInfo : NSObject
@property (nonatomic, weak) NSObject  *observer;
@property (nonatomic, copy) NSString    *keyPath;
@end
复制代码

@implementation LGInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    if (self=[super init]) {
        _observer = observer;
        _keyPath  = keyPath;
    }
    return self;
}
@end
复制代码

在添加观察者时咱们就能够:

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context{
    
    // 1: 验证是否存在setter方法 : 不让实例进来
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa的指向 : LGKVONotifying_LGPerson
    object_setClass(self, newClass);
    // 4: 保存观察者信息
    LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    if (!observerArr) {
        observerArr = [NSMutableArray arrayWithCapacity:1];
        [observerArr addObject:info];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}
复制代码

 在子类setter方法里:

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    // 4: 消息转发 : 转发给父类
    // 改变父类的值 --- 能够强制类型转换
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue       = [self valueForKey:keyPath];
    
    void (*lg_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)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
    // 1: 拿到观察者
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    for (LGKVOInfo *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 & LGKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & LGKeyValueObservingOptionOld) {
                    [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,change,NULL);
            });
        }
    }
    
}
复制代码

这样代码是否是更好一些,设计更合理.

3.2 KVO自定义中加入函数式编程设计思想

在刚刚的设计中咱们经过添加abserver以后,咱们还需从-(void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object,获得属性的变化,这种设计方法是否是太麻烦了.咱们能够加入函数式编程设计思想,用block直接回调属性变化结果.这样接口和API须要稍微修改下:

@interface LGInfo : NSObject
@property (nonatomic, weak) NSObject  *observer;
@property (nonatomic, copy) NSString    *keyPath;
@property (nonatomic, copy) LGKVOBlock  handleBlock;
@end

@implementation LGInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{
    if (self=[super init]) {
        _observer = observer;
        _keyPath  = keyPath;
        _handleBlock = block;
    }
    return self;
}
@end

复制代码

lg_addObserver方法改为: 

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{

    // 1: 验证是否存在setter方法 : 不让实例进来

    [self judgeSetterMethodFromKeyPath:keyPath];

    // 2: 动态生成子类

    Class newClass = [self createChildClassWithKeyPath:keyPath];

    // 3: isa的指向 : LGKVONotifying_LGPerson

    object_setClass(self, newClass);

    // 4: 保存信息

    LGInfo *info = [[LGInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];

    

    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));

    if (!mArray) {

        mArray = [NSMutableArray arrayWithCapacity:1];

        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    [mArray addObject:info];
}
复制代码

派生类中:lg_setter里直接用block进行回调.

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    // 4: 消息转发 : 转发给父类
    // 改变父类的值 --- 能够强制类型转换
    void (*lg_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)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
    
    // 5: 信息数据回调
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    for (LGInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
            info.handleBlock(info.observer, keyPath, oldValue, newValue);
        }
    }
}

复制代码

补充下:这两年苹果底层不少api ,也加入了函数式编程思想.好比NSTimer

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))复制代码

3.3 KVO 自动销毁机制

刚刚自定的时候,每次须要本身手动移除观察者,有时候盆友可能忘了,会致使崩溃异常等一系列后果,在这里咱们就想能不能自动销毁.

在这里咱们先思考下,该何时销毁?

有的盆友确定会想到当观察者(一般是VC)释放时,就该销毁对象的监听属性.不错,其实能不能更靠前,当Person 的派生类对象都释放的时候,咱们是否是就应该能够销毁了.

因此咱们能够监听Person 的派生类 dealloc 方法.可是这个dealloc 方法在NSOBject的分类里,若是在这里面之间操做的话、其它地方(VC)引用了这个头文件没有使用KVO的,当这个(VC)里的NSObject对象释放时也会来到这个方法. 

因此方法一:

还记得 createChildClassWithKeyPath ,这个动态实现子类的方法吗?

在这里咱们重写了 set、class 方法 ,如今再重写dealloc 方法,

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 防止重复建立生成新类
    if (newClass) return newClass;
    /**
     * 若是内存不存在,建立生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */
    // 2.1 : 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 : 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 : 添加class : class的指向是LGPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
    // 2.3.2 : 添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_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)lg_dealloc, deallocTypes);

    return newClass;
}

static void lg_dealloc(id self,SEL _cmd){
    Class superClass = [self class];
    object_setClass(self, superClass);
}复制代码

这样就能够自动释放啦,因此你们知道为何系统KVO在生成派生子类时,会重写dealloc方法了吧.

第二种方法:

系统NSObject 原本就有dealloc方法,我为啥要重写?不重写的话,若是在这里面之间操做的话、其它地方(VC)引用了这个头文件没有使用KVO的,当这个(VC)里的NSObject对象释放时也会来到这个方法.因此咱们用到 MethodSwizzle.

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    // 防止重复建立生成新类
    if (newClass) return newClass;
    /**
     * 若是内存不存在,建立生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */
    // 2.1 : 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 : 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 : 添加class : class的指向是LGPerson
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getInstanceMethod([self class], classSEL);
    const char *classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
    // 2.3.2 : 添加setter
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)lg_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)lg_dealloc, deallocTypes);
  
    // 方法交换
    [self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
    
    return newClass;
}
复制代码

- (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
 //   Class cls = self;
    Class cls = [self class];
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!swiMethod) {
        return NO;
    }
    if (!oriMethod) {
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }
    
    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    return YES;
}
复制代码

- (void)myDealloc{

    Class superClass = [self class];
    object_setClass(self, superClass);
    // 不会形成循环递归,下面调用myDealloc等于调用dealloc
    [self myDealloc];
}
复制代码

四.总结

本文经过自定义实现一套简化版的KVO进一步加深了对KVO实现原理的理解。固然一套完善的KVO并无这么简单,里面还没考虑到线程安全、锁、观察属性以keypath等状况。代码中确定还有许多漏洞,在这里给你们推荐比较成熟的自定义KVO.FaceBook开发设计好的(FBKVOController),你们有兴趣能够去看下.

相关文章
相关标签/搜索