KVO(Key-Value-Observer)也就是观察者模式,是苹果提供的一套事件通知机制。容许对象监听另外一个对象特定属性的改变,并在改变时接收到事件,通常继承自NSObject
的对象都默认支持KVO
。函数
KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。区别在于:
一、相对于被观察者和观察者之间的关系,KVO是一对一的,而不一对多的。也就是kvo监听到被观察属性值改变时只会通知到观察者,是一对一的关系。而通知模式则是在被观察值改变的时候发送全局通知,任何对象均可以接听到这个通知,是一个一对多的关系;
二、KVO对被监听对象无侵入性,不须要修改其内部代码便可实现监听。而通知须要在被监听对象改变的时候添加发送通知代码。
二、使用atom
一、spa
//1.注册观察者 /* - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; observer:观察者 也就是被观察对象发生改变时通知的接收者 keyPath:被观察的属性名 好比咱们这里是age属性 options:参数 这里通常选择NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld 也就是在回调方法里会受到被观察属性的旧值和新值,默认为只接收新值。若是想在注册观察者后,当即接收一次回调,则能够加入NSKeyValueObservingOptionInitial枚举。 context:这个参数能够传入任意类型的对象,这个值会传递到接收消息回调的代码中,是KVO中的一种传值方式。 */ [self.per1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
二、代理
//2.实现通知回调方法 当被观察对象的属性值发生变化时 就会回调这个方法 change字典中存放KVO属性相关的值,根据options时传入的枚举来返回。 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@---%@----%@---%@",keyPath,object,change,context); }
三、指针
//3.移除监听 [self.per1 removeObserver:self forKeyPath:@"age"];
KVO
的addObserver
和removeObserver
须要是成对的,若是重复remove
则会致使NSRangeException
类型的Crash
,若是忘记remove
则会在观察者释放后再次接收到KVO
回调时Crash
。code
苹果官方推荐的方式是,在init
的时候进行addObserver
,在dealloc
时removeObserver
,这样能够保证add
和remove
是成对出现的,是一种比较理想的使用方式。server
调用KVO
属性对象时,不只能够经过点语法和set
语法进行调用,KVO
兼容不少种调用方式:(关于KVC的实现原理接下来会讲到)对象
// 1.经过属性的点语法间接调用 self.per1.age = 123;
//2. 直接调用set方法 [self.per1 setAge:123];
// 3.使用KVC的setValue:forKeyPath:方法 [self.per1 setValue:@123 forKeyPath:@"age"]; //4. 使用KVC的setValue:forKey:方法 [self.per1 setValue:@123 forKey:@"age"]; // 5.经过mutableArrayValueForKey:方法获取到代理对象,并使用代理对象进行操做
若是直接修改对象的成员变量是不会触发KVO的:blog
//PersonClass.h文件 #import <Foundation/Foundation.h> @interface PersonClass : NSObject{
@public; NSInteger _age;//成员变量 } //属性 @property (nonatomic, assign) NSInteger age; @end
直接修改为员变量,咱们发现没有触发KVO继承
self.person1 -> _age = 234;
上面全是监听一些基础的数据类型 当被观察属性是一个复杂对象时,好比如今person对象有一个属性animal,那么kvo会如何监听呢?
#import <Foundation/Foundation.h> @class AnimalClass; @interface PersonClass : NSObject @property (nonatomic, assign) NSInteger age; @property (nonatomic, strong) AnimalClass *animal; @end
AnimalClass类中有一个name属性
@interface AnimalClass : NSObject @property (nonatomic, copy) NSString *name; @end
当咱们对animal这个属性进行监听时,发现当对animal的属性值(name)修改时 kvo并不会监听到, 而当给person对象从新赋值一个新的animalClass对象时会被监听到
//会监听到改变 由于person1的animal属性是个指针 存储的是animal类型的一个地址值 当从新赋值一个alloc出来的新animalClass对象时 animal的地址值发生了改变 会调用person1的setAnimal方法 AnimalClass *ani2 = [[AnimalClass alloc]init]; ani2.name = @"cat"; self.person1.animal = ani2; //不会被kvo监听到 由于修改animal的name属性 根本没有调用person1的setAnimal方法 只是调用了animal的setName方法 self.person1.animal.name = @"cat";
而当咱们对person1.animal对象的name属性进行监听时 是能够监听到 self.person1.animal.name = @"cat";这种值改动的
[self.person1.animal addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
因此kvo可否监听到变化 要看这个被监听对象存储的是什么?其实是否发生了改变?
咱们在经过runtime函数object_getclass分别打印person1在添加kvo先后的类对象分别是是PersonClass和NSKVONotifying_PersonClass;
也就是在person1对象注册了kvo之后,其类对象发生了改变
咱们在改变age值的时候 其实是调用了setAge方法 而实例对象调用方法是根绝isa指针找到类对象的对象方法列表找到对应的方法进行调用,因此kvo的本质其实是重写了被观察属性值的set方法
NSKVONotifying_PersonClass类对象set方法的具体实现:(_NSSet*ValueAndNotify的内部实现)
didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法
KVO
是经过isa-swizzling
技术实现的(这句话是整个KVO
实现的重点)。在运行时利用RuntimeAPI动态生成一个根据原类建立的中间类(命名规则是NSKVONotifying_xxx
的格式),这个中间类是原类的子类,并动态修改当前对象的isa
指向中间类。
首先重写set方法。在set方法里分别调用willChangeValueForKey->set的赋值操做->didChangeValueForKey 其中didChangeValueForKey在内部视线中会调用观察者的回调方法 返回被观察对象的相关参数
而且将class
方法重写,返回原类的Class(PersonClass类)
。这是由于苹果不想暴露kvo的内部实现,建议在开发中不该该依赖isa
指针,而是经过class
实例方法来获取对象类型。
_isKVOA
方法,这个方法能够当作使用了KVO
的一个标记,系统可能也是这么用的。若是咱们想判断当前类是不是KVO
动态生成的类,就能够从方法列表中搜索这个方法。
KVO
在属性发生改变时的调用是自动的,若是在被观察属性值没有改变的状况下手动调用kvo 那么须要时候调用willChangeValueForKey和didChangeValueForKey两个方法(两个方法必须都进行调用 系统在执行didChangeValueForKey方法前会检测willChangeValueForKey是否被调用了)
[self.person1 willChangeValueForKey:@"age"]; [self.person1 didChangeValueForKey:@"age"];
手动触发的前提是这个对象已经添加了kvo 若是没有添加的话kvo是没法知道观察者是谁的 也就是不会回调观察者的- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{}这个回调方法的