KVO介绍

作者: 深度码农患者 | 来源:发表于2020-02-22 21:41 被阅读0次
    概述

    KVO是苹果提供的一套事件通知机制,允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
    KVO和NSNotification都是iOS中对观察者模式的一种实现。区别在于,相对于观察者和被观察者的关系,KVO是一对一的,而不是一对多的。KVO对被监听对象无侵入性,不需要修改其内部代码就可以实现监听。
    KVO可以监听单个属性的变化,也可以监听集合对象的变化。

    实现原理

    KVO是通过isa-swizzling技术实现的,在运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa指针指向中间类。将Class方法重写,返回原类的Class,所以苹果建议在开发中不应该依赖isa指针,而是通过class实例方法来获取对象类型。

    缺点

    苹果提供的KVO自身存在很多问题,首先问题在于,KVO如果使用不当很容易崩溃,比如说重复的add和remove导致的崩溃,Observer被释放导致的崩溃,keypath传错导致的崩溃等等。
    由于keyPath是字符串的形式,所以在其对象的属性名发生改变之后,字符串没有被改变很容易导致崩溃。我们可以利用系统的反射机制将keyPath反射出来,这样编译器可以在@selector()中进行合法性检查。

    自己实现KVO
    1. 可以在NSObject的分类中实现
    2. 实现addObserver:forKey:with:方法
    • 检查对象的类有没有响应的setter方法,如果没有则抛出异常。
    • 检查对象isa指指针指向的类是不是一个KVO类,如果不是,新建一个继承当前类的子类,并把isa指针指向这个类。
    • 检查对象的KVO类是否重写过setter方法,如果没有,添加重写的setter方法
    • 添加观察者
      具体来说,有以下步骤:
    1. 通过setterFirGeter方法获得相应的setter的方法名,也就是key的首字母大写,然后再前面加载set后面加上:,变成setKey:。然后再用class_getInstanceMethod去获取setKey:的实现,如果没有,再抛出异常。
    2. 看类名有没有我们定义的前缀,如果没有,我们去创建新的子类,并通过object_setClass()去修改isa指针。
      动态创建新的类需要runtime中定义的objc_allocateClassPair()函数。传一个父类名以及额外的空间,会返回一个雷,然后给这个类添加方法和变量。
    - (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
    {
        NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
        Class clazz = NSClassFromString(kvoClazzName);
    
        if (clazz) {
            return clazz;
        }
    
        // class doesn't exist yet, make it
        Class originalClazz = object_getClass(self);
        Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
    
        // grab class method's signature so we can borrow it
        Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
        const char *types = method_getTypeEncoding(clazzMethod);
        class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    
        objc_registerClassPair(kvoClazz);
    
        return kvoClazz;
    }
    
    1. 重写setter方法。新的setter在调用原setter方法后,通知每个观察者。
    - (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
    {
        NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
        Class clazz = NSClassFromString(kvoClazzName);
    
        if (clazz) {
            return clazz;
        }
    
        // class doesn't exist yet, make it
        Class originalClazz = object_getClass(self);
        Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
    
        // grab class method's signature so we can borrow it
        Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
        const char *types = method_getTypeEncoding(clazzMethod);
        class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    
        objc_registerClassPair(kvoClazz);
    
        return kvoClazz;
    }
    
    1. 把观察的相关信息存在associatedObject中。
    @interface PGObservationInfo : NSObject
    
    @property (nonatomic, weak) NSObject *observer;
    @property (nonatomic, copy) NSString *key;
    @property (nonatomic, copy) PGObservingBlock block;
    
    @end
    

    相关文章

      网友评论

          本文标题:KVO介绍

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