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回调给观察者
网友评论