KVO详解

2020-03-04  本文已影响0人  kennths

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回调给观察者

上一篇下一篇

猜你喜欢

热点阅读