美文网首页
自定义KVO

自定义KVO

作者: 瞬间完善 | 来源:发表于2020-02-19 18:03 被阅读0次

    上篇文章KVO我们探究了KVO的一些使用方法和其原理,今天我们来自定义一个KVO
    首先我们创建一个NSObject的分类NSObject (LGKVO),我们知道KVOaddObserverremoveObserver需要是成对的,我们重写一下这两个方法:

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

    首先我们要知道自定义lg_addObserver中要做什么操作:

      1. 通过Method判断是否有这个key对应的selector,如果没有则Crash,也就是检查方法的合法性。
      1. 判断当前类是否是KVO子类,如果不是则创建,并设置其isa指针。
      1. 如果没有实现,则添加Key对应的setter方法。
      1. 将调用对象添加到数组中,保存observer信息。

    第一步:进行方法的合法性检查

    这里我们自定义judgeSetterMethodFromKeyPath进行检查:

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

    这里第二步和第三步放一起了:

    // 2: 动态生成子类
        Class newClass = [self createChildClassWithKeyPath:keyPath];
        // 3: isa的指向 : LGKVONotifying_LGPerson
        object_setClass(self, newClass);
    
    #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 ,如果没有实现,则添加Key对应的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;
    }
    

    我们使用KVO时候,是对操作的类(例如LGPerson)的属性赋值,这里因为将isa指向了新创建的中间子类(NSKVONotifying_LGPerson),所以这里需要调用父类(LGPerson)setter方法进行属性赋值。我们这里通过objc_msgSendSuper发送消息来实现。
    属性改变后我们还需要通过observeValueForKeyPath方法通知观察者。我们仍然是使用消息发送来实现通知观察者。

    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);
                });
            }
        }
    }
    

    当对象销毁的时候会调用dealloc,在该方法中将isa支持重新指向原来的类。

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

    第四步:

    将调用对象添加到数组中,保存observer信息:

    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);
        }
    

    这一步中要先创建一个继承NSObject的类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
    

    .m中:

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

    到这里是不是觉得我们自定义KVO结束了,如果感觉结束了,那就文章最开始的时候没有认真看哦,addObserverremoveObserver需要是成对的哦,所以,我们还需要重写removeObserver方法。

    自定义removeObserver

    传入观察者和keyPath,当观察者所有keyPath都移除后则从KVO中移除观察者对象。

    - (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
        
        NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        if (observerArr.count<=0) {
            return;
        }
        
        for (LGInfo *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);
        }
    }
    

    相关文章

      网友评论

          本文标题:自定义KVO

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