美文网首页iOS记录
iOS 自定义KVO的实现

iOS 自定义KVO的实现

作者: 冼同学 | 来源:发表于2021-09-17 15:14 被阅读0次

    前言

    KVO原理分析一文中已经说明了KVO的原理以及相关的使用案例,那么我们知道了KVO的原理,我们有能力实现自定义的KVO吗?答案是可以的,那么我们就进入下一步的探索咯。

    准备工作

    键值观察编程指南
    函数式编程

    自定义KVO的实现

    主要实现自定义的三个方法

    @interface NSObject (HP_KVO)
    
    - (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    - (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
    - (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    
    @end
    

    1.确认大致步骤

    • 1.验证是否存在setter方法 : 不让实例进来
    • 2.动态生成子类
    • 3.改变isa的指向 :LGKVONotifying_LGPerson
    • 4.保存观察者

    首先我们查看外部的调用,代码如下:

    - (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: 保存观察者信息 - 收集信息 数组集合
        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);
        }
    }
    

    2.验证是否存在setter方法

    KVO监听的其实是setter方法的调用,所以首先的判断setter方法是否存在

    #pragma mark - 验证是否存在setter方法
    - (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];
        }
    }
    

    3.动态生成子类

    动态生成一个类的思路步骤:

    • 申请类
    • 注册类
    • 添加方法

    注意:前面说过动态生成的中间类--分类是不会销毁的,所以必须要先判断中间类是否已经存在

    
    static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
    
    #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
        //根据keypath 生成sel
        SEL classSEL = NSSelectorFromString(@"class");
        //给当前类 添加Method  关联sel 
        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;
    }
    
    3.1重写class方法
    Class lg_class(id self,SEL _cmd){
        return class_getSuperclass(object_getClass(self));
    }
    
    3.2重写setter方法

    通过存储的LGKVOInfokey值进行匹配,从而对change进行赋值,然后把消息发送出去

    static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
    
    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.3 setterForGetterget方法获取set方法的名称
    #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];
    }
    
    3.4 getterForSetterset方法获取getter方法的名称
    #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];
    }
    

    4.保存观察者信息

    4.1 创建LGKVOInfo类,用于保存观察者信息
    • LGKVOInfo.h
    typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
    
        LGKeyValueObservingOptionNew = 0x01,
        LGKeyValueObservingOptionOld = 0x02,
    };
    
    @interface LGKVOInfo : NSObject
    @property (nonatomic, weak) NSObject  *observer;
    @property (nonatomic, copy) NSString    *keyPath;
    @property (nonatomic, assign) LGKeyValueObservingOptions options;
    
    - (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options;
    @end
    
    • LGKVOInfo.m
    @implementation LGKVOInfo
    - (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options{
        self = [super init];
        if (self) {
            self.observer = observer;
            self.keyPath  = keyPath;
            self.options  = options;
        }
        return self;
    }
    
    @end
    

    5.移除观察者

    就是把isa指回父类,同时也要把之前保存的LGKVOInfo对象也要通过观察者移除

    - (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
        
        NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        if (observerArr.count<=0) {
            return;
        }
        
        for (LGKVOInfo *info in observerArr) {
            if ([info.keyPath isEqualToString:keyPath]) {
                [observerArr removeObject:info];
                objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                break;
            }
        }
    
        if (observerArr.count<=0) {
            // 指回给父类
            Class superClass = [self class];
            object_setClass(self, superClass);
        }
    }
    

    6.自定义的KVO完成,测试一下

     self.person = [[LGPerson alloc] init];
        [self.person lg_addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.person.nickName = @"XJL";
    }
    
    #pragma mark - KVO回调
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"%@",change);
    }
    
    - (void)dealloc{
        [self.person lg_removeObserver:self forKeyPath:@"nickName"];
    }
    
    打印结果
    2021-07-31 16:53:48.964893+0800 003---自定义KVO[6549:278301] 来了:XJL
    2021-07-31 16:53:48.965016+0800 003---自定义KVO[6549:278301] 来到 LGPerson 的setter方法 :XKL
    2021-07-31 16:53:48.965151+0800 003---自定义KVO[6549:278301] {
        nickName = XJL;
    }
    

    自定义的KVO引入函数式

    函数式编程的优势:

    • 代码简洁,开发快速
    • 接近自然语言,易于理解
    • 更方便的代码管理
    • 易于"并发编程"
    • 代码的热升级

    实话实话,无非就是把监听的回调方法在block中去实现,并且把block保存在LGInfo中,这样对应LGInfo信息发生变化,即可执行block

    1.定义block

    typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
    
    - (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block;
    

    2.给每个属性添加回调的block

    @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
    

    3.去掉之前回调方法的实现,改成block回调

    - (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];
    }
    

    4.setter方法
    调用setter方法时,找到对应的LGInfo执行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);
            }
        }
    }
    

    KVO自动销毁

    系统自带的KVO是需要手动销毁的,这里我们做点特别的,就是KVO自动销毁操作。

    1.通过方法交换实现

    通过分析可知,要想实现自动销毁,那么必然是需要对dealloc方法进行一些处理的,Method swizzling就能做到这样子的骚操作。

    1.1+load方法中单例调用交换方法
    + (void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
    
            [self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
        });
    }
    
    1.2交换方法实现
    + (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
        Class cls = self;
        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;
    }
    
    1.3 myDealloc实现
    - (void)myDealloc{
        Class superClass = [self class];
        object_setClass(self, superClass);
        [self myDealloc];
    }
    

    主要是做了isa重定位父类的这一步动作。虽然这样确实是可以实现KVO的自动释放,但是有个很严重的问题是,dealloc 作为一个系统方法,几乎所有的类在释放时都会调用的方法,不是所有的类都需要这样去改变isa指向的,所以这样做,肯定不合适,那么我们下面用其他的方法进行解决。

    2.重写dealloc方法

    对象在要销毁时,都会调用dealloc方法,而作为被观察者监听的对象,他实际监听的是派生类LGKVONotifying_LGPerson,自然而然我们所看到的调用的dealloc方法,实际也是调用子类的dealloc方法,所以我们只需要重写dealloc方法,即可实现自动销毁机制,并且也没有影响父类和其他类dealloc方法的调用。

    2.1添加dealloc方法
    #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);
        // 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;
    }
    
    2.2 实现lg_dealloc方法

    在这里把isa指回父类,即可

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

    验证是否能实现自动销毁操作

    代码验证1
    验证isa是否能指向父类
    验证2
    最后发现isa指向了原来的类,KVO自动销毁功能完成。

    相关文章

      网友评论

        本文标题:iOS 自定义KVO的实现

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