美文网首页
iOS-自定义KVO

iOS-自定义KVO

作者: 似水流年_9ebe | 来源:发表于2021-08-01 02:38 被阅读0次

    前言

    iOS-KVO原理分析这篇文章我们分析了KVO的原理,接下来,我们自己动手简单实现一个KVO。

    准备工作

    首先建一个RoKVO的工程,再建一个RoViewController,在storyboard中关联起来,如图:


    1

    KVO自定义思路

    我们知道KVO是通过添加NSObject的分类的实现,我们也模仿一下,新建一个NSObject+RoKVO的分类文件,然后我们再模护系统的添加和移除观察的观察的方法。如下:

    - (void)ro_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
    
    - (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    

    思路如下:

    1. 判断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];
        }
    }
    

    ro_addObserver这个方法中进行判断。

    1. KVO 核心:isa_siwzilling
      1. 1 申请类
      2. 2 添加方法
      3. 3 注册类
    2. isa指向交换
    3. setter 业务逻辑

    动态生成子类

    ro_addObserver 调用*- (Class)createChildClassWithKeyPath:(NSString )keyPath创建子类,后面会详细讲这个函数。
    接着进行isa交换,代码如下:

    object_setClass(self, newClass);
    

    我们再讲createChildClassWithKeyPath这个核心功能。
    首先我们要先申请类,调用* *这个API。
    代码如下:

    NSString *oldClassName = NSStringFromClass([self class]);
        NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
        Class newClass = NSClassFromString(newClassName);
        // 判断是否存在,存在直接返回
        if (newClass) return newClass;
        // 创建类
        objc_allocateClassPair([self class],newClassName.UTF8String, 0);
    

    接着我们去注册类,如下

     objc_registerClassPair(newClass);
    

    接着我们再讲下这个方法的核心功能,添加方法,如下:

    - (Class)createChildClassWithKeyPath:(NSString *)keyPath {
        NSString *oldClassName = NSStringFromClass([self class]);
        NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
        Class newClass = NSClassFromString(newClassName);
        // 判断是否存在,存在直接返回
        if (newClass) return newClass;
        // 创建类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        
        // 添加方法 setter
        SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
        Method  setterMethod = class_getInstanceMethod([self class], setterSel);
        const char *setterType = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSel, (IMP)ro_setter, setterType);
        
        // 添加方法 class
        SEL classSel = NSSelectorFromString(@"class");
        Method  classMethod = class_getClassMethod([self class], classSel);
        const char *classType = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, setterSel, (IMP)ro_class, classType);
        
        // 注册类
        objc_registerClassPair(newClass);
        return newClass;
    }
    

    自定义KVO的setter

    子类动态创建完了,我们来看下setter方法,如何重写。
    由于篇幅过长,我们这里直接发代码,代码会有相关的注释。
    如下:

    static void ro_setter(id self,SEL _cmd,id newValue) {
        // 往父类发送消息
        struct objc_super ro_objc_super;
        ro_objc_super.receiver = self;
        ro_objc_super.super_class = class_getSuperclass(object_getClass(self));
        
        void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
        ro_msgSendSuper(&ro_objc_super,_cmd,newValue);
        
        // 既然观察到了,下一步是回调,接着让我们的观察者调用
        //拿到观察者
        id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(RoKVOAssiociateKey));
        
        // 2: 消息发送给观察者
        SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
        NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
        objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
    }
    

    自定义KVO的多元素观察和移除观察

    当我们需要观察多个元素的时候,要怎么处理呢,接下来我们来看下。
    我们添加了一个RoKVOInfo用来存储观察KVO的数据的存储(代码会在结尾发出来)。
    如何观察多元素呢,如ro_addObserver代码:

    - (void)ro_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
        if (keyPath.length ==0 ) {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"keyPath不能为空"] userInfo:nil];
        }
        // 判断setter方法是否存在
        [self judgeSetterMethodFromKeyPath:keyPath];
        // 动态生成子类
        Class newClass = [self createChildClassWithKeyPath:keyPath];
        
        // isa指针交换
        object_setClass(self, newClass);
        
        //保存KVO信息,收集信息
        RoKVOInfo *info = [[RoKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:(RoKeyValueObservingOptions)options];
        NSMutableArray *array = objc_getAssociatedObject(self,(__bridge const void *)(roKVOAssiociateKey));
        if (!array) {
            array = [NSMutableArray arrayWithCapacity:1];
        }
        [array addObject:info];
        objc_setAssociatedObject(self, (__bridge const void *)(roKVOAssiociateKey), array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    ro_setter的代码:

    static void ro_setter(id self,SEL _cmd,id newValue) {
        // 通过cmd(sel)获取keyPath
        NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
        id oldValue       = [self valueForKey:keyPath];
        // 往父类发送消息
        struct objc_super ro_objc_super;
        ro_objc_super.receiver = self;
        ro_objc_super.super_class = class_getSuperclass(object_getClass(self));
        
        void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
        ro_msgSendSuper(&ro_objc_super,_cmd,newValue);
        
     
        NSMutableArray *array = objc_getAssociatedObject(self,
                                                        (__bridge  const void *)roKVOAssiociateKey);
        // 遍历查找是setter方法在观察
        for (RoKVOInfo *info in array) {
            if ([info.keyPath isEqualToString:keyPath]) {
                dispatch_async(dispatch_queue_create(0, 0), ^{
                    NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
                    // 对新旧值进处处理
                    if (info.options  & RoKeyValueObservingOptionNew) {
                        [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                    }
                    if (info.options & RoKeyValueObservingOptionOld) {
                        [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                        if (oldValue) {
                            [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                        }
                    }
                    void (*ro_objc_msgSend)(NSObject *observer,SEL , NSString *keyPath, id, NSMutableDictionary *change, void *context) = (void *)objc_msgSend;
                    // 消息发送给观察者
                    SEL observerSel = @selector(ro_observeValueForKeyPath:ofObject:change:context:);
                    ro_objc_msgSend(info.observer,observerSel,keyPath,self,change,NULL);
                    
                });
            }
        }
    }
    

    那么我们该如何移除我们的观察呢?
    我们知道,KVO在移除观察时,会交换isa,那么我们模访官方的做法,如下代码:

    - (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
        NSMutableArray *array = objc_getAssociatedObject(self,(__bridge const void *)(roKVOAssiociateKey));
        if (array.count <=0) {
            return;
        }
        for (RoKVOInfo *info in array) {
            if ([info.keyPath isEqualToString:keyPath]) {
                [array removeObject:info];
                objc_setAssociatedObject(self, (__bridge const void *)(roKVOAssiociateKey), array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                break;
            }
        }
        
        if (array.count <=0) {
            Class superClass = [self class];
            object_setClass(self, superClass);
        }
    }
    

    自定义KVO的函数式

    我们知道KVO观察写的代码要多,一个回调会有很多if else的这种结构,那么我们怎么优化呢,当然可以通过函数式编程要优化,我们可以通过block实现。

    1. 定义一个block *typedef void(^RoKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
    1. 这个block需要存储在RoKVOInfo中。
    2. 发送消息的时候,直接使用block传数据。
      函数式是映射数学上的函数概念,是定义域映射到值域上的关系,函数可以做为参数传参,也可以作为返回值

    自定义KVO自动销毁

    我们知道KVO是不会自动销毁的,需要手动销毁,我们实现了KVO的函数式,那么我们可以实现自动销毁吗?答案 是肯定的。下面我们来实现下。
    流程:

    1. 重写dealloc方法
      2.在重写的dealloc方法中释放。

    结语

    以个就是个在实现的一个简单的KVO,还有很多不完善的地方,后续会发出来一个比较完整的自定义的KVO。

    附上KVO的代码

    RoKVOInfo.h文件代码

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef void(^RoKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
    
    
    @interface RoKVOInfo : NSObject
    @property (nonatomic, weak) NSObject  *observer;
    @property (nonatomic, copy) NSString    *keyPath;
    @property (nonatomic, copy) RoKVOBlock  block;
    
    - (instancetype)initWitObserver:(NSObject *)observer
                         forKeyPath:(NSString *)keyPath
                              block:(RoKVOBlock)block;
    @end
    
    NS_ASSUME_NONNULL_END
    

    RoKVOInfo.m文件代码

    #import "RoKVOInfo.h"
    
    @implementation RoKVOInfo
    - (instancetype)initWitObserver:(NSObject *)observer
                         forKeyPath:(NSString *)keyPath
                              block:(RoKVOBlock)block {
        self = [super init];
        if (self) {
            _observer = observer;
            _keyPath  = keyPath;
            _block = block;
        }
        return self;
    }
    @end
    

    NSObject+RoKVO.h文件代码

    #import <Foundation/Foundation.h>
    #import "RoKVOInfo.h"
    NS_ASSUME_NONNULL_BEGIN
    
    
    @interface NSObject (RoKVO)
    
    - (void)ro_addObserver:(NSObject *)observer
                forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
                     block:(RoKVOBlock)block;
    
    
    - (void)ro_removeObserver:(NSObject *)observer
                   forKeyPath:(NSString *)keyPath;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    NSObject+RoKVO.m文件代码

    #import "NSObject+RoKVO.h"
    #import <objc/message.h>
    static NSString *const roKVOPrefix = @"ROKVONotifying_"; //动态子类的前缀
    static NSString *const roKVOAssiociateKey = @"ROKVO_AssiociateKey";
    
    @implementation NSObject (RoKVO)
    
    
    
    - (void)ro_addObserver:(NSObject *)observer
                forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
                     block:(RoKVOBlock)block {
        if (keyPath.length ==0 ) {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"keyPath不能为空"] userInfo:nil];
        }
        [self judgeSetterMethodFromKeyPath:keyPath];
        // 动态生成子类
        Class newClass = [self createChildClassWithKeyPath:keyPath];
        // isa的指向 : LGKVONotifying_LGPerson
        object_setClass(self, newClass);
        // 保存信息
        RoKVOInfo *info = [[RoKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath block:block];
        NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
        if (!mArray) {
            mArray = [NSMutableArray arrayWithCapacity:1];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        [mArray addObject:info];
    }
    
    
    - (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
        NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
        if (observerArr.count<=0) {
            return;
        }
        
        for (RoKVOInfo *info in observerArr) {
            if ([info.keyPath isEqualToString:keyPath]) {
                [observerArr removeObject:info];
                objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                break;
            }
        }
        
        if (observerArr.count<=0) {
            // 指回给父类
            Class superClass = [self class];
            object_setClass(self, superClass);
        }
    }
    
    #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];
        }
    }
    
    #pragma mark - 动态创建子类
    - (Class)createChildClassWithKeyPath:(NSString *)keyPath {
        NSString *oldClassName = NSStringFromClass([self class]);
        NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
        Class newClass = NSClassFromString(newClassName);
        // 防止重复创建生成新类
        if (newClass) return newClass;
        
        // 申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        // 注册类
        objc_registerClassPair(newClass);
        // 添加class
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)ro_class, classTypes);
        //  添加setter
        SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
        Method setterMethod = class_getInstanceMethod([self class], setterSEL);
        const char *setterTypes = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSEL, (IMP)ro_setter, setterTypes);
        //  添加dealloc
        SEL deallocSEL = NSSelectorFromString(@"dealloc");
        Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
        const char *deallocTypes = method_getTypeEncoding(deallocMethod);
        class_addMethod(newClass, deallocSEL, (IMP)ro_dealloc, deallocTypes);
     
        return newClass;
        
    }
    
    static void ro_dealloc(id self,SEL _cmd){
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
    
    #pragma mark - setter
    static void ro_setter(id self,SEL _cmd,id newValue){
        NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
        id oldValue = [self valueForKey:keyPath];
        // 消息转发 转发给父类
        // 改变父类的值 --- 可以强制类型转换
        void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
        struct objc_super superStruct = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self)),
        };
        //objc_msgSendSuper(&superStruct,_cmd,newValue)
        ro_msgSendSuper(&superStruct,_cmd,newValue);
        
        // 信息数据回调
        NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
        
        for (RoKVOInfo *info in mArray) {
            if ([info.keyPath isEqualToString:keyPath] && info.block) {
                info.block(info.observer, keyPath, oldValue, newValue);
            }
        }
    }
    
    Class ro_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 - 从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];
    }
    
    @end
    
    

    相关文章

      网友评论

          本文标题:iOS-自定义KVO

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