美文网首页
07-KVO的底层分析

07-KVO的底层分析

作者: iOS之文一 | 来源:发表于2021-10-24 17:39 被阅读0次

    OC底层原理探索文档汇总

    主要内容:
    KVO的使用
    KVO的底层实现

    查阅KVO官方文档

    key-value-observing(键值观察)
    简单来说就是通过一个key来找到某个属性并监听其值的改变,KVC是实现KVO的基础,因为需要键值监听,KVO只能实现属性的监听,也就是有setter方法的监听,其他的比如成员变量是不行的。

    KVO的简单使用

    普通的属性设置

    1. 添加观察者
    [self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
    

    参数:

    • observer:观察者,通常是自己,也可以是任何人
    • keyPath:观察的内容,也就是属性名,这里写成字符串的形式(由此也可以知道KVO是基于KVC的)
    • option:
      • 响应类型,也就是在何种情况下会进行响应,决定了通知中的内容和发送时间。
      • 常用的就是NSKeyValueObservingOptionNew,也就是更新属性值
    • context:
      • 表示上下文环境,在观察响应时传递一些额外的信息
      • 一般可以用它来判断观察的属性是哪个
      • context不能用nil填充,因为是viod *,所以需要用null
      • context为了区分每一次观察的细节,不直接通过对象和keyPath来判断,这样比较麻烦,因为还需要进行大量对象的判断,这样写起来复杂又麻烦,用context可以性能好,而且可读性好

    说明:

    • 把person的nick属性添加观察者,观察者就是当前对象
    • context用来传递额外信息

    option:

    NSKeyValueObservingOptionNew:change字典包括改变后的值
    NSKeyValueObservingOptionOld:change字典包括改变前的值
    NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
    NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
    
    
    1. 在观察者中实现监听方法
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id>*)change context:(void *)context{
    

    参数:
    keyPath:监听的属性
    object:被监听者
    change:表示数据修改的方式:
    context:添加监听时的上下文参数

    说明:

    • 这个方法是一个是系统会调用的,当观察到属性发生变化时就自动调用
    • 它是针对属性的监听后进行的响应
    • NSKeyValueChangeKey用来设置观察到属性改变的方式,比如给数组插入数据,比如给一个属性更新一个数据

    NSKeyValueChangeKey类型

    typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
        NSKeyValueChangeSetting = 1,//设值
        NSKeyValueChangeInsertion = 2,//集合插入值
        NSKeyValueChangeRemoval = 3,//集合移除值
        NSKeyValueChangeReplacement = 4,//集合替换值
    };
    
    1. 移除观察者
    [a removeObserver:b forKeyPath:];
    

    说明:

    • 一般都在dealloc,也就是销毁该对象的时候
    • 一定要移除的,不移除就会崩溃

    为什么不移除观察者会崩溃:

    • 如果没有移除,当被观察者的属性发生改变时,会通知查找观察者,而如果观察者已经被注销掉了,就无法通知到位,野指针异常了

    属性依赖

    如果我们想要监听一个属性,但是这个属性的变化不是直接修改的,而是通过其他几个属性的修改来影响的,这就是属性依赖。
    也就是说我们通过监听其他值,响应目标值。两个值的观察变成一个值的观察,观察到两个值中只要有一个进行变化,即进行结果返回。

    其他的都和普通的实现一样,重点在于keyPathsForValuesAffectingValueForKeyAPI的使用

    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
        
        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
        //判断是真正要监听的那个属性
        if ([key isEqualToString:@"downloadProgress"]) {
            //添加keyPaths,这个数组里的每个属性发生变化都会响应观察
            NSArray *affectingKeys = @[@"totalData", @"writtenData"];
            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
        }
        return keyPaths;
    }
    
    • 通过方法系统提供的类方法keyPathsForValuesAffectingValueForKey来实现的,最终返回keyPaths,而这个keyPaths里放的就是需要监听的那几个属性
    • 当这个属性添加观察后,KVO会自动检测Set中的所有keyPaths

    数组的设置

    重点需要注意数组元素改变和数组本身的改变是不一样的,对于集合类型的操作一定要注意我们操作的方式,这里需要用到KVC来观察数组属性的改变。

    代码:

    // 5: 数组观察
        self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
        //观察者数组的KVO,必须利用KVC的原理机制才可以观察到
        [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
    

    说明:

    • 对于数组的观察,需要使用KVC来进行设置才可以监听到

    手动和自动设置开关

    系统默认支持KVO的通知发送,同时系统也提供了一个入口,让我们可以手动去设置是否需要支持发送,如果对这个类关闭了自动开关后,还可以针对其中的某个属性进行手动开启。
    手动设置开关可以让我们灵活的加上自己想要的判断条件

    代码:

    // 自动开关
    + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    //    return YES;//自动
        return NO;//手动,自己写setter
    }
    //这两个方法都是系统自动监听时要使用的方法,如果不自动监听,就需要我们自己加了。
    - (void)setNick:(NSString *)nick{
        [self willChangeValueForKey:@"nick"];
        _nick = nick;
        [self didChangeValueForKey:@"nick"];
    }
    

    说明:

    • 重写这个方法,如果返回YES,就说明是自动的,如果返回NO,是手动的
    • 如果需要对某个属性进行观察,可以在其setter方法中添加willChangeValueForKey和didChangeValueForKey方法

    小结:

    1. KVO的使用都是三步曲,添加观察者,观察响应,移除观察者
    2. 我们可以自己控制是否需要进行观察
    3. 对于数组的观察需要使用KVC进行设值
    4. 通过属性依赖,可以对有依赖性质的属性进行观察

    KVO的底层实现

    核心就是新建了一个中间类,是当前类的子类,在setter方法中增加了两个方法,这样就可以进行观察了。

    底层的实现过程

    底层实现图.png
    • 在添加观察后,系统会动态的生成一个新的中间类
    • 中间类中有被观察的属性的setter方法
    • setter方法包含三条件语句
      • 1、通知观察者值即将改变[self willChangeValueForKey:@""];
      • 2、修改值[super setValue: forKey:];
      • 3、通知观察者值已经改变[self didChangeValueForKey@""];

    注意:

    • 观察的只是属性,不能观察成员变量,因为观察属性其实也观察的是setter方法
    • 重新创建一个当前类的子类,这个子类的命名是这样的:NSKVONotifying_LGPerson
    • 移除观察后,不会删除这个中间类,只要注册到内存中,就会一直存在
    • 创建了中间类后,对象的isa会变成中间类,也就是我们观察的对象会指向中间类
      • 这里值得注意的是此时这个对象通过class返回的仍然是原来的类
      • 移除观察后还会指回之前的类

    相关文章

      网友评论

          本文标题:07-KVO的底层分析

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