美文网首页iOS
iOS底层探索之KVO(三)—自定义KVO

iOS底层探索之KVO(三)—自定义KVO

作者: 俊而不逊 | 来源:发表于2021-08-06 08:53 被阅读0次

    回顾

    在前两篇博客中,已经介绍了KVO的相关操作,还有KVO的底层逻辑是通过动态生成子类,重写父类的方法实现的,那么我们如何自定义一个KVO呢?

    iOS底层探索之KVO(一)—KVO简介
    iOS底层探索之KVO(二)—KVO原理分析

    主题

    1. 前期分析

    系统的KVO是在NSObject的上面拓展了一些能力,如下图所示:

    在这里插入图片描述

    系统的KVO使用的三部曲是:

    • 添加监听addObserver
    • 监听回调observeValueForKeyPath
    • 移除监听removeObserver

    我们也仿照系统的API自定义一个KVO,如下:

    @interface NSObject (JP_KVO)
    
    - (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    - (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
    - (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    
    @end
    

    我们自定义KVO是对属性进行观察,那么成员变量是没有setter方法的,所以第一步就得过滤掉成员变量。如何动态生成子类,改变isa的指向,保存观察者,步骤大致如下:

    1. 验证是否存在setter方法 : 不让成员变量(实例变量)进来
    2. 动态生成子类
    3. 改变子类的isa的指向
    4. 保存我们的观察者
    - (void)jp_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的指向
        object_setClass(self, newClass);
        // 4: 保存观察者
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KJPKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    2. 验证是否存在setter方法

    • judgeSetterMethodFromKeyPath:keyPath
    - (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
        Class superClass    = object_getClass(self);
        SEL setterSeletor   = NSSelectorFromString(setterForGetter(keyPath));
        Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
        if (!setterMethod) {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"不好意思没有当前%@的setter",keyPath] userInfo:nil];
        }
    }
    

    判断是否有setter方法,没有就抛出异常信息

    3. 动态生成子类

    - (Class)createChildClassWithKeyPath:(NSString *)keyPath{
        
        NSString *oldClassName = NSStringFromClass([self class]);
        NSString *newClassName = [NSString stringWithFormat:@"%@%@",kjpKVOPrefix,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的指向是JPPerson
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)jp_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)jp_setter, setterTypes);
        return newClass;
    }
    
    • JP_class 返回父类信息
    Class JP_class(id self,SEL _cmd){
        return class_getSuperclass(object_getClass(self));
    }
    
    • 判断是否已经创建了子类,没有就创建
    • 申请类
    • 注册类
    • newClass不存在则调用objc_allocateClassPair创建kvo子类,并且重写- class方法
    • 添加setter
    • 返回newClass

    4. 创建setter

    创建setter方法主要是分两部分:调用父类方法以及发送通知

    static void jp_setter(id self,SEL _cmd,id newValue){
        // 4: 消息转发 : 转发给父类
        // 改变父类的值 --- 可以强制类型转换
        void (*jp_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)
        jp_msgSendSuper(&superStruct,_cmd,newValue);
        
        // 既然观察到了,下一步不就是回调 -- 让我们的观察者调用
        // - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
        // 1: 拿到观察者
        id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kJPKVOAssiociateKey));
        
        // 2: 消息发送给观察者
        SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
        NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
        objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
    }
    
    
    • 调用父类方法可以通过objc_msgSendSuper实现,调用父类的setter(也可以通过performSelector调用

    • 通知观察者keypath可以通过_cmd转换获取,objectselfchange也可以获取到,context可以先不传。那么核心就是observer的获取。

    • 通过关联对象的方式,把observer进行存储

    • 拿到观察者了,就把消息发送给观察者,进行信息的回调处理

    • getterForSetter

    #pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
    static NSString *getterForSetter(NSString *setter){
        
        if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
        
        NSRange range = NSMakeRange(3, setter.length-4);
        NSString *getter = [setter substringWithRange:range];
        NSString *firstString = [[getter substringToIndex:1] lowercaseString];
        return  [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
    }
    

    更多内容持续更新

    🌹 喜欢就点个赞吧👍🌹

    🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

    🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

    相关文章

      网友评论

        本文标题:iOS底层探索之KVO(三)—自定义KVO

        本文链接:https://www.haomeiwen.com/subject/balbvltx.html