发现手动实现KVO的一个坑

Posted JackLee18

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了发现手动实现KVO的一个坑相关的知识,希望对你有一定的参考价值。

  最近项目开发用到了派生子类的知识,从网上看了一下手动实现KVO的源码,网上发现大家的源码实现差不多。好像都是从一个大佬那里抄来的。
参考博客地址如下:
https://tech.glowing.com/cn/implement-kvo/
https://www.jianshu.com/p/bf053a28accb
源码地址:https://github.com/Jerry4me/JRCustomKVODemo
运行了一下源码发下源码中对与非Object类型的属性进行监听时,会崩溃。比如age,NSInteger类型。具体出问题的代码如下:

/**
 *  重写setter方法, 新方法在调用原方法后, 通知每个观察者(调用传入的block)
 */
static void jr_setter(id self, SEL _cmd, id newValue)

    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = [self getterForSetter:setterName];
    
    
    if (!getterName) 
        NSLog(@"找不到getter方法");
    
    
    // 获取旧值
    id oldValue = [self valueForKey:getterName];
    
    // 调用原类的setter方法
    
    struct objc_super superClazz = 
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    ;
    
    ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);
    
    // 为什么不能用下面方法代替上面方法?
    //    ((void (*)(id, SEL, id))objc_msgSendSuper)(self, _cmd, newValue);
    
    
    // 找出观察者的数组, 调用对应对象的callback
    NSMutableArray *observers = objc_getAssociatedObject(self, JRAssociateArrayKey);
    // 遍历数组
    for (JRObserverInfo *info in observers) 
        if ([info.key isEqualToString:getterName]) 
            // gcd异步调用callback
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
                info.callback(info.observer, getterName, oldValue, newValue);
            );
        
    

崩溃情况如下:


可以看到id其实是objec_object类型的指针,而age是基础数据类型,因此函数调用到这里的时候出现了崩溃。
为了解决这个问题,我祭出了万能指针 void * ,主要原理就是利用void 可以指向基础类型的内存地址。因此可以通过void进行传值。
修改后的代码如下:

/**
 *  重写setter方法, 新方法在调用原方法后, 通知每个观察者(调用传入的block)
 */
static void jr_setter(id self, SEL _cmd, void* newValue)

    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = [self getterForSetter:setterName];
    
    
    if (!getterName) 
        NSLog(@"找不到getter方法");
    
    
    // 获取旧值
    id oldValue = [self valueForKey:getterName];
    
    // 调用原类的setter方法
    
    struct objc_super superClazz = 
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    ;
    
    ((void (*)(void *, SEL, void*))objc_msgSendSuper)(&superClazz, _cmd, newValue);
    
    // 为什么不能用下面方法代替上面方法?
    //    ((void (*)(id, SEL, id))objc_msgSendSuper)(self, _cmd, newValue);
    
    
    // 找出观察者的数组, 调用对应对象的callback
    NSMutableArray *observers = objc_getAssociatedObject(self, JRAssociateArrayKey);
    // 遍历数组
    for (JRObserverInfo *info in observers) 
        if ([info.key isEqualToString:getterName]) 
            // gcd异步调用callback
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
               // info.callback(info.observer, getterName, oldValue, newValue);
               //此处需要处理非object类型转NSValue的操作。
            );
        
    

回调的时候需要处理非NSObject类型转换为NSValue的操作,由于类型太多,我这里就不展开了,大家感兴趣的可以自己实现一下。后面对各种基础类型的处理还有很多
更多干货文章,可以扫描二维码关注公众号

以上是关于发现手动实现KVO的一个坑的主要内容,如果未能解决你的问题,请参考以下文章

通过子类实现KVO,浅析KVO底层原理

iOS底层探索之KVO—KVO原理分析

iOS底层探索之KVO—自定义KVO

iOS底层探索之KVO—自定义KVO

iOS KVO监听数组元素的变化

KVO