• 自定义KVO


    1. 不调用实例变量的方法

    2. 动态生成子类 (利用runtime生成:申请类,添加一些方法-set-class等方法,注册类 )

    ****常量类型不能添加观察者

     

    #import <Foundation/Foundation.h>

     

    NS_ASSUME_NONNULL_BEGIN

     

    @interface NSObject (FXKVO)

     

    - (void)fx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

     

    - (void)fx_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;

     

    - (void)fx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

    @end

     

    NS_ASSUME_NONNULL_END

     

    添加一个NSObject的分类,重写观察者方法

     

    #pragma mark - 验证是否存在setter方法,如果不存在抛出异常(例如成员变量添加观察者会崩溃)

    - (void)fx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{

        //1. 验证set方法

        [self judgeSetterMethodFromKeyPath:keyPath];

        //2. 动态生成子类

        Class newClass = [self creatChildClass];

        //3. 当前对象的类,isa指向newClass

        object_setClass(self, newClass);

       //4. 观察者(为当前类添加属性,来记录观察者,用来做回调的时候使用)

        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

     

    #pragma mark - 动态生成子类

    - (Class)creatChildClass{

     // LGKVO_LGPerson

        

        // 2. 动态生成子类

         

        NSString *oldName = NSStringFromClass([self class]);

        NSString *newName = [NSString stringWithFormat:@"%@%@", FXKVOPrefix, oldName];

        Class newClass    = objc_allocateClassPair([self class], newName.UTF8String, 0);

        

        //申请注册到内存中

        objc_registerClassPair(newClass);

        

        // 2.1 子类添加一些方法 class setter

        // class

        SEL classSEL = NSSelectorFromString(@"class");

        Method classM = class_getInstanceMethod([self class], classSEL);

        const char *type = method_getTypeEncoding(classM);

        class_addMethod(newClass, classSEL, (IMP)fx_class, type);

     

        // setter

        SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));

        Method setterM = class_getInstanceMethod([self class], setterSEL);

        const char *settertype = method_getTypeEncoding(setterM);

        class_addMethod(newClass, setterSEL, (IMP)fx_setter, settertype);

        

        return newClass;

    }

     

    #pragma mark - 函数部分

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

     

        NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));

     

        // 消息发送 setName:

        [self willChangeValueForKey:keyPath];

     

        // newValue给谁, newClass KVOPerson

        // 给父类发送消息

        void (*fx_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;

        struct objc_super fx_objc_super = {

            .receiver = self,

            .super_class = [self class],

        };

        fx_msgSendSuper(&fx_objc_super, _cmd,newValue);

     

        [self didChangeValueForKey:keyPath];

     

        //响应回调 -- KVOVC(观察者) --调用某个方法 -- obbserve

        //把观察者存起来,先用属性存起来(关联存储)

        id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey));

        //响应

        //sel

        SEL obserSEL = @selector(observeValueForKeyPath:ofObject:change:context:);

        ((id (*)(id, SEL, id, id, id, id))objc_msgSend)(observer, obserSEL, keyPath,self,@{keyPath:newValue},NULL);

    }

     

    //注意这里不能写成class_getSuperclass([self class]); //会递归崩溃 [self class]的底层imp是 <=> class_getSuperclass([self class]) 所以会无限递归

    Class fx_class(id self,SEL _cmd){

        return class_getSuperclass(object_getClass(self));

    }

     

    #pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:

    static NSString *setterForGetter(NSString *getter){

        

        if (getter.length <= 0) { return nil;}

        

        NSString *firstString = [[getter substringToIndex:1] uppercaseString];

        NSString *leaveString = [getter substringFromIndex:1];

        

        return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];

    }

     

    #pragma mark - 验证是否存在setter方法

    - (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{

        Class superclass = object_getClass(self);

        SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));

        Method setterMethod = class_getInstanceMethod(superclass, setterSelector);

        if(!setterMethod) {

            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter", keyPath] userInfo:nil];

        }

    }

     

    #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];

    }

     

    运行结果:(与系统KVO打印信息相同)

    目前还存在的问题:

    1. 只能添加一个观察者对象 

    2. 目前考虑是否可以使用block回调(代码逻辑和功能逻辑放到一起)

    3. 每次添加网观察者还需要手动移除,是否可以实现自动移除

    后面还需要优化(在下一篇博客中优化)

    4. 为什么要生成动态子类,并且重写方法(谈下自己的理解,互不影响,是一中设计,这个动态子类是随时都会消失,是一个临时的子类,减少对原类的入侵,不影响本类,就像我们会新建子类处理一些子类的逻辑,而不影响父类一样)

     比较成功的KVO框架(FBKVO)

  • 相关阅读:
    前端面试:Vue.js常见的问题
    Web前端攻击方式及防御措施
    JavaScript代码规范
    bind、apply、call的理解
    Markdown标记语言简介及使用方法
    github个人主页的建立
    深度理解“高内聚低耦合”
    私有云与公有云的区别
    响应式网页设计
    redis和mongodb比较
  • 原文地址:https://www.cnblogs.com/coolcold/p/12078068.html
Copyright © 2020-2023  润新知