美文网首页
iOS-底层原理-自定义KVO

iOS-底层原理-自定义KVO

作者: 如意神王 | 来源:发表于2021-12-22 13:51 被阅读0次

    1.自定义KVO

    1.上一篇博客了解了iOS 系统KVO的底层实现原理,那么这里进行自定义KVO,更好的理解原理和熟悉一些runtime的c方法的调用和功能,利用NSObject分类来实现

    2.大体步骤如下

    1.验证是否是属性,非属性不进行处理

    2.保存观察者,分类多数时候用到关联对象

    3.动态生成子类LGKVONotifying_本类名,前缀没什么限制
    3.1 申请类
    3.2 注册类
    3.3 添加一些方法,这里主要添加 setter class
    3.4 isa指向子类LGKVONotifying_本类名

    4.子类LGKVONotifying_本类名的setter方法处理
    4.1 给父类发送setter消息
    4.2 给观察者发送回调消息

    3.详细代码

    3.1 注册观察者,外面调用者是Person类,observer是controller

    - (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGNSKeyValueObservingOptions)options {
        
        // 1.验证是否存在setter方法:不让成员变量进来
        [self judgeSetterMethodFromKeyPath:keyPath];
        
        // 2.保存观察者信息,用于发送消息
        
        // *****这里的知识点*****
        // 2.1 分类里面使用关联对象进行保存比较多
        // 2.2 LGKVOInfo数据模型里面是弱引用,关联对象是强引用,这样既没有对observer进行强引用,又达到了保存的目的
        NSMutableArray * infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        if (!infoArray) {
            infoArray = [NSMutableArray arrayWithCapacity:1];
        }
        LGKVOInfo * info = [[LGKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath options:options];
        [infoArray addObject:info];
        
        // 关联对象保存 观察者信息数组
        objc_setAssociatedObject(self, (__bridge  const void * _Nonnull)(kLGKVOAssiociateKey), infoArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        // 3.生成派生子类
        // 新类名为 LGKVONotifying_Person
        NSString * oldClassName = NSStringFromClass([self class]);
        NSString * newClassName = [NSString stringWithFormat:@"%@%@", kLGKVOPrefix, oldClassName];
        
        Class newClass = NSClassFromString(newClassName);
        
        // 这个if只会进来一次
        if (!newClass) {
            
            // 3.1申请类 开辟新的类 size 大小为0,根据父类创建名称为newClassName的新类,开辟空间为0,objc_allocateClassPair申请子类
            newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
            
            // 3.2注册到内存
            objc_registerClassPair(newClass);
            
            // 3.3添加class方法 // 不写class setter方法的IMP会循环进入
            SEL classSel = @selector(class);
            Method classMethod = class_getInstanceMethod([self class], classSel);
            const char * classType = method_getTypeEncoding(classMethod);
            class_addMethod(newClass, classSel, (IMP)lg_class, classType);
            
            // 3.4 isa 指向新的类 LGKVONotifying_本类名
            object_setClass(self, newClass);
        }
        
        // 3.3添加setter方法,这里只模拟iOS系统的 setter和class方法
        SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
        Method method = class_getInstanceMethod([self class], setterSel);
        const char * type = method_getTypeEncoding(method);
        class_addMethod(newClass, setterSel, (IMP)lg_setter, type);
    }
    

    3.2 class方法IMP

    // 子类LGKVONotifying_LGPerson class方法的IMP
    Class lg_class(id self, SEL _cmd) { // 为了返回LGPerson
        return class_getSuperclass(object_getClass(self));
    }
    

    3.3 setter方法IMP

    
    // 子类LGKVONotifying_LGPerson setter方法的IMP
    static void lg_setter(id self, SEL _cmd, id newValue) {
        NSLog(@"LGKVONotifying_LGPerson setter IMP newValue == %@", newValue);
    
        // 获取旧的值,设置新值之前获取旧值
        NSString * keyPath = getterForSetter(NSStringFromSelector(_cmd));
        id oldValue = [self valueForKeyPath:keyPath];
        NSLog(@"oldValue == %@", oldValue);
    
        
        // 1.给父类发送对应的setter方法
        // 通用封装解决依赖性,发送消息到父类,不用关注到底是哪个类
        void (*lg_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
        struct objc_super superStruct = {
            .receiver = self,
            .super_class = [self class],
        };
        lg_msgSendSuper(&superStruct, _cmd, newValue);
        
        
        // 2.给observer发送回调消息
        // 2.1 找到keyPath对应的observer
        // 2.2 获取新值旧值发送给observer
        NSMutableArray * mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        for (LGKVOInfo * info in mArray) {
            NSMutableDictionary<NSKeyValueChangeKey,id> * change = [NSMutableDictionary dictionaryWithCapacity:1];
            if ([info.keyPath isEqualToString:keyPath]) {
                if (info.options & LGNSKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                } else {
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    }
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
    
                id observer = info.observer;
                if (observer && [observer respondsToSelector:@selector(lg_observeValueForKeyPath:ofObject:change:)]) {
                    [observer lg_observeValueForKeyPath:keyPath ofObject:info.observer change:change];
                }
            }
        }
    }
    
    

    3.4移除观察者

    // 移除观察者

    - (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
        
        // 删除对应的observer
        NSMutableArray * mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        for (LGKVOInfo * info in mArray) {
            if ([info.keyPath isEqualToString:keyPath] && [info.observer isEqual:observer]) {
                [mArray removeObject:info];
                NSLog(@"removeObject mArray == %@ count == %lu", mArray, (unsigned long)[mArray count]);
                break;
            }
        }
        
        // 对应的observer没有观察的属性了,isa指向父类
        BOOL isHadObserver = [self isKVOA:observer];
        if (isHadObserver == NO) {
            // 指回父类
            Class superClass = [self class];
            object_setClass(self, superClass);
        }
    }
    
    // 判断该observer还有没有其他需要观察的属性
    // 这个类似系统的isKVOA:方法,有几个属性同时观察,必须全部移除后isa指针才会指向父类
    - (BOOL)isKVOA:(NSObject *)observer {
    
        NSMutableArray * mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
        BOOL isHadObserver = YES;
        for (LGKVOInfo * info in mArray) {
            if ([info.observer isEqual:observer]) {
                isHadObserver = NO;
                break;
            }
        }
        
        NSLog(@"last mArray == %@ count == %lu", mArray, (unsigned long)[mArray count]);
    
        // 关联对象保存 观察者信息数组
        objc_setAssociatedObject(self, (__bridge  const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        return isHadObserver;
    }
    

    3.5其他辅助方法

    // 验证是否存在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];
        }
    }
    
    // 从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];
    }
    
    // 从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];
    }
    

    3.6调用自定义KVO

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        self.view.backgroundColor = [UIColor whiteColor];
        [self createButton];
        
        
        self.person = [[Person alloc] init];
        [self.person lg_addObserver:self forKeyPath:@"nickName" options:LGNSKeyValueObservingOptionOld];
        [self.person lg_addObserver:self forKeyPath:@"name" options:LGNSKeyValueObservingOptionOld];
    
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        count++;
        self.person.nickName = [NSString stringWithFormat:@"%lu", (unsigned long)count];
    }
    
    - (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change {
        NSLog(@"change == %@", change);
    }
    
    - (void)createButton {
        UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.backgroundColor = [UIColor redColor];
        button.frame = CGRectMake(100, 100, 100, 100);
        [button setTitle:@"remove" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
    }
    
    - (void)buttonPressed:(UIButton *)button {
        [self.person lg_removeObserver:self forKeyPath:@"nickName"];
        [self.person lg_removeObserver:self forKeyPath:@"name"];
    }
    

    3.7输出

    2021-12-17 17:51:24.740939+0800 KVO_18_Custom[92333:7137885] LGKVONotifying_LGPerson setter IMP newValue == 9
    2021-12-17 17:51:24.741213+0800 KVO_18_Custom[92333:7137885] getNickName in Person
    2021-12-17 17:51:24.741356+0800 KVO_18_Custom[92333:7137885] oldValue == 8
    2021-12-17 17:51:24.741438+0800 KVO_18_Custom[92333:7137885] setNickName: in Person
    2021-12-17 17:51:24.741637+0800 KVO_18_Custom[92333:7137885] change == {
        new = 9;
        old = 8;
    }
    2021-12-17 17:51:24.914475+0800 KVO_18_Custom[92333:7137885] LGKVONotifying_LGPerson setter IMP newValue == 10
    2021-12-17 17:51:24.914663+0800 KVO_18_Custom[92333:7137885] getNickName in Person
    2021-12-17 17:51:24.914760+0800 KVO_18_Custom[92333:7137885] oldValue == 9
    2021-12-17 17:51:24.914813+0800 KVO_18_Custom[92333:7137885] setNickName: in Person
    2021-12-17 17:51:24.914966+0800 KVO_18_Custom[92333:7137885] change == {
        new = 10;
        old = 9;
    }
    2021-12-17 17:51:25.107261+0800 KVO_18_Custom[92333:7137885] LGKVONotifying_LGPerson setter IMP newValue == 11
    2021-12-17 17:51:25.107462+0800 KVO_18_Custom[92333:7137885] getNickName in Person
    2021-12-17 17:51:25.107644+0800 KVO_18_Custom[92333:7137885] oldValue == 10
    2021-12-17 17:51:25.107745+0800 KVO_18_Custom[92333:7137885] setNickName: in Person
    2021-12-17 17:51:25.107955+0800 KVO_18_Custom[92333:7137885] change == {
        new = 11;
        old = 10;
    }
    

    4.一切看起来很完美,实则隐藏着大坑,当系统的KVO和自定义的KVO同时使用就会崩溃或者其他问题出现

    4.1 混合调用

        [self.person lg_addObserver:self forKeyPath:@"nickName" options:LGNSKeyValueObservingOptionOld];
        [self.person lg_addObserver:self forKeyPath:@"name" options:LGNSKeyValueObservingOptionOld];
        
        [self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:NULL];
    

    4.2子类isa指向出现问题,而且会递归调用setter方法导致崩溃


    iOS系统KVO和自定义KVO同时使用子类紊乱.jpeg

    4.3解决方案
    基于系统派生类自定义KVO,这种思路不需要创建自定义的派生类,代码实现上与自定义派生类KVO大同小异,先调用系统方法生成系统派生类,再修改系统派生类的setter方法IMP,IMP里面分别给父类和观察者发送消息

    4.4基于系统派生类自定义KVO 具体代码,addObserver和removeObserver都是系统的,只改变setter的IMP

    4.5注册KVO代码

    - (void)addObserver:(NSObject*)observer forKeyPath:(NSString *)keyPath changedBlock:(_EasyKVOChangedBlock)block {
        if (!observer || keyPath.length < 1) return;
    
        /* 使用系统方法获得派生类 */
        [self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    
        /* 保存 block 信息 */
        [_tipsMap(self, _cmd) setObject:[block copy] forKey:[NSString stringWithFormat:@"_%@_%@_block", NSStringFromClass(object_getClass(self)), keyPath]];
        
        /*  改变setter方法 */
        NSString *format = [keyPath stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[keyPath substringToIndex:1] uppercaseString]];
        NSLog(@"format == %@", format);
        
        // NSString转SEL @selector
        SEL setSel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", format]);
    
        Method setMethod = class_getInstanceMethod(object_getClass(self), setSel);
        
        // 交换NSKVONotifying_Person ---> setter方法的IMP,使其指向自定义setter方法的IMP
        // 也就是把系统的这个类的NSKVONotifying_Person setter方法IMP换成我们自己的IMP,这样系统的和自定义的KVO的setter方法全部指向了自定义的IMP
        // 优点是只改了 IMP,其他dealloc class _isKVOA方法完全没有动
        // 优点 addObserver removeObserver都是系统的方法,生命周期完全不用干涉
        // 缺点是 1.向父类发送setter消息没有改变,但是,对observer发送消息系统的也走了我们自定义的方法或者block回调
        // 如果addObserver系统的KVO那么,回调还是会走系统的,如果addObserver了自定义的KVO,那么都会走自定义的block,多少有点乱,一会这个回调一会那个回调
        // 系统的和自定义的method交换
        class_replaceMethod(object_getClass(self), setSel, (IMP)_setterFunction, method_getTypeEncoding(setMethod));
    }
    

    4.6自定义setter的IMP

    void _setterFunction(id self, SEL _cmd, id newValue) {
        NSString *setterName = NSStringFromSelector(_cmd);
        if (setterName.length < 4) return;
        
        NSString *format = [setterName substringWithRange:NSMakeRange(3, setterName.length -4)];
        NSString *keyPath = [format stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[format substringToIndex:1] lowercaseString]];
        if (keyPath.length < 1) return;
        
        // 1.发送父类setter消息
        // 获取旧的值
        id oldValue = [self valueForKeyPath:keyPath];
        if (![oldValue isEqual:newValue]) {
            //调用父类setter
            struct objc_super supercls = {
                .receiver = self,
                .super_class = class_getSuperclass(object_getClass(self))
            };
            void (* msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
            msgSendSuper(&supercls, _cmd, newValue);
        }
        
      //  发送观察者消息
        _EasyKVOChangedBlock block = (_EasyKVOChangedBlock)[_tipsMap(self, _cmd) objectForKey:[NSString stringWithFormat:@"_%@_%@_block", NSStringFromClass(object_getClass(self)), keyPath]];
        if (block) block(newValue, oldValue);
    }
    

    4.7移除观察者

    - (void)removeObserver:(NSObject *)observer blockForKeyPath:(NSString *)keyPath {
        [self removeObserver:observer forKeyPath:keyPath];
        NSString *blockKeyName = [NSString stringWithFormat:@"_%@_%@_block", @"NSKVONotifying_", keyPath];
        NSMutableDictionary *tips = _tipsMap(self, _cmd);
        [tips removeObjectForKey:blockKeyName];
    }
    

    4.8观察者信息保存

    NSMutableDictionary *_tipsMap(id self, SEL _cmd) {
        NSMutableDictionary * _tipsDic = objc_getAssociatedObject(self, &__EasyKVOTipsDic);
        if (!_tipsDic) {
            _tipsDic = [[NSMutableDictionary alloc] init];
            objc_setAssociatedObject(self, &__EasyKVOTipsDic, _tipsDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        return _tipsDic;
    }
    

    4.9总结,如果使用了这种自定义的KVO那么系统的KVO也会指向自定义的block回调,系统本身的回调不会执行,如果只使用系统的KVO则没有任何影响。

    相关文章

      网友评论

          本文标题:iOS-底层原理-自定义KVO

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