KVO详解

作者: kennths | 来源:发表于2020-03-04 15:11 被阅读0次

    KVO (Key-Value Observing) 是Cocoa提供的一种基于KVC的机制,允许一个对象去监听另一个对象的某个属性,当该属性改变时系统会去通知监听的对象(不是被监听的对象)。它是建立子KVC之上的。

    [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:"person_name"];

    这是一个添加观察者的方法,首先它的Options有四种可以设置,通过官方文档可以得知

    NSKeyValueObservingOptionNew:提供更改前的值

    NSKeyValueObservingOptionOld:提供更改后的值

    NSKeyValueObservingOptionInitial:观察最初的值(在注册观察服务时会调用一次触发方法)

    NSKeyValueObservingOptionPrior:分别在值修改前后触发方法(即一次修改有两次触发)

    这么四种值,可以按需使用,一般使用较多的是NSKeyValueObservingOptionNew。

    那么还有另外一个属性context。

    很多人喜欢直接设置为NULL,当然这是在观察的属性变量较少的时候,使用是没有什么问题的,但是当有子类,父类,观察相同的属性变量的时候,在

    - (void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void*)context{

    }

    中判断回来值进行相应处理的时候,就会要嵌套多层判断,影响性能,而context可以设置为一个指针,是全局唯一的,通过它设置,可以快速定位,减少相应嵌套判断。

    在处理完delloc中,必须移除对应的观察者,这是一个良好的代码习惯,如果不移除,在观察单利的对象时,如果多次观察会出现奔溃。

    自动观察

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{

        return YES;

    }

    通过设置这个方法,默认为YES。返回YES,则开启自动观察类中所有变量,这里可以进行判断,移除不想观察的属性。

    手动观察

    - (void)setName:(NSString*)name{

        [self willChangeValueForKey:@"name"];

        _name= name;

        [self didChangeValueForKey:@"name"];

    }

    如果关闭了自动观察,我们还想观察到该对象的值变化,那么就需要在某个属性的set方法中,调用willChangeValueForKey,和didChangeValueForKey来开启手动观察。

    观察路径集合

    如果在项目中,某几个或者多个的值会影响到其中一个值的变化的时候,如果按常规的写法,那么我们需要观察多个对象,这样观察的对象就会变很多,那么这时我们就可以使用观察路径集合。

    例如,一个进度条的Progress需要依赖时间,写入数据和总数据来进行计算得出结果,那么我们只需要观察的Progress就可以了。

    +(NSSet<NSString *>*)keyPathsForValuesAffectingValueForKey:(NSString *)key{

        NSSet *keyPath = [super keyPathsForValuesAffectingValueForKey:key];

        if([keyisEqual:@"progress"]) {

            NSArray *affectKeys = @[@"time",@"totalDatas",@"writeDatas"];

            keyPath = [keyPathsetByAddingObject:affectKeys];

        }

        returnkeyPath;

    }

    这样就可以在time,totalDatas,writeDatas某一个值发生变化的时候,就可以让progress值发生改变回调。

    集合数组观察

    通过了解KVC,我们知道KVC中集合的取赋值和普通属性变量的取赋值是不一样的,必须要符合KVC的赋值取值方法,才能使得KVO中的观察者得到值的变化后发生回调。

    [self.person.dataArray addObject:@"joo"];

    如果一个类中有一个dataArray的属性,我们在注册了观察后,我们这样添加数据,那么在回调方法中会回调吗,答案是不会的,因为这样加入一个数据,虽然是真实加入了,但是这样添加不符合KVC的赋值流程,因为我们观察的是dataArray,但是这样添加,它不会添加到dataArray中去,可能是_dataArray,isdataArray中,所以我们必须要使用KVC的设置方式来加入数据

        [[self.person mutableArrayValueForKey:@"dataArray"] addObject:@"hello"];

    通过KVC的取值 取出dataArray 然后加入数据,这样就能进过观察者回调。

    KVO实现原理

    首先我们猜想是不是通过setter方法才有回调的。通过设置属性变量和实例变量,我们分别对其进行观察,发现只有属性变量有回调。所以它是默认观察setter方法的,也就是必须要实现有setter方法才可以进行观察。

    然后通过打印在类初始化时候的所有类和子类,然后发现在添加观察后,它的子类发生了变化,发现它会多出一个NSKVONotifying_xxx的子类。所以经过添加观察后,是会动态添加子类的。

    那么这个子类在构建的过程中,发生了什么变化呢,它的变量列表ivarList,methodList方法列表,IMP方法指针有没有发生变化呢。

    通过打印NSKVONotifying_xxx的ivarList发现里面没有一个属性,所以它没有添加任何新的变量。

    通过打印NSKVONotifying_xxx的methodList和IMP的地址,会发现观察的变量的setter方法的IMP发生了改变,说明它是进行了重写的。另外还发现新加了 class ,dealloc,_isKVOA等方法。那就是新增了class,dealloc,_isKVOA方法。

    那么我们可以来看一下它的实现流程:

    1.验证是否有setter方法,实例对象就不处理。

    2.动态生成子类:

    2.1 : 动态开辟一个新类:NSKVONotifying_xxx

    2.2 :注册这个新类 

    2.3 : 添加class : class的指向是原有的类

    2.4 实现class,dealloc,_isKVOA方法

    2.5 : setter方法 : 通过对setter赋值 实现父类的方法 self.xxx = @"xx";

    2.6:objc_getAssociatedObject 关联住我们的观察者

    3. isa的指向 : NSKVONotifying_xxx

    4. 消息转发给父类

    5.通过objc_getAssociatedObject关联的观察者将处理的value,keypath回调给观察者

    相关文章

      网友评论

          本文标题:KVO详解

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