1、KVO是基于Runtime机制实现的;
2、当某个类的对象的某个属性第一次被观察时,系统会在运行期间动态地创建该类的一个派生类,在这个派生类中重写基类的任何被观察属性的setter方法,派生类在被重写的setter方法内实现真正的通知机制;
3、如果原类为Person,那么生成的派生类名为NSKVONotifying_Person;
4、我们知道,每一个对象都有一个isa指针(即第一个成员变量)指向当前类,所以系统就是在当一个类的对象第一次被观察的时候,偷偷的将isa指针指向动态生成的派生类,从而在 “被监听属性” 赋值时执行的是派生类的setter方法;
5、此外,苹果还偷偷重写了此派生类的class方法,这时候 po personObj,打印的依旧是Person,但使用po object_getClass(personObj),就可以打印出对象的类型NSKVONotifying_Person;这是苹果为了隐藏生成的派生类,让我们误以为还是使用的当前类;
6、键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值;而当改变发生后,didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
7、注意:willChangeValueForKey:和didChangevlueForKey:缺一不可;
直接拿代码来说明吧,说了一大堆,估计大家都看的不知所以然。
1、场景:我们声明了一个Person类,里面有两个属性,name和age,我们要使用kvo来监听Person对象的age发生的变化;
2、创建Person类,声明属性:
1 #import <Foundation/Foundation.h> 2 3 /** 4 Person模型类 5 */ 6 @interface Person : NSObject 7 8 /** 9 姓名 10 */ 11 @property(nonatomic,copy) NSString *name; 12 13 /** 14 年龄 15 */ 16 @property(nonatomic,assign) NSInteger age; 17 18 @end
3、创建Person对象,添加KVO监听对象age属性
1 #import "ViewController.h" 2 #import "Person.h" 3 4 @interface ViewController () 5 6 @end 7 8 @implementation ViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 13 Person *per = [[Person alloc] init]; 14 per.name = @"xiaoming"; 15 per.age = 18; 16 17 // 观察per对象的age属性的变化情况 18 [per addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:NULL]; 19 20 // 修改age属性的值,将会被观察到 21 per.age = 20; 22 23 // 这里po per的结果:<Person: 0x608000026960> 24 // 但是:po object_getClass(per)的结果:NSKVONotifying_Person 25 // 所以:苹果偷偷重写了派生类的class方法,但用运行时可以查看当前对象所属类型 26 27 // 移除KVO 28 [per removeObserver:self forKeyPath:@"age"]; 29 } 30 31 /** 32 KVO监听:当被监听对象的被监听属性变化后调用 33 34 @param keyPath 被监听的属性 35 @param object 被监听的对象 36 @param change 变化情况 37 @param context 携带的参数,此示例传入NULL即可 38 */ 39 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 40 { 41 NSLog(@"%@对象的%@属性发生变化了%@",object,keyPath,change); 42 43 // 打印结果: 44 // <Person: 0x608000026960>对象的age属性发生变化了{ 45 // kind = 1; 46 // new = 20; 47 // } 48 } 49 50 51 @end
其实,实现KVO的是这2个方法:willChangeValueForKey:和didChangevlueForKey:
我们只要调用这两个方法,属性值不变化,也会被观察到。
1、给Person类添加一个方法kvoTest:
1 #import <Foundation/Foundation.h> 2 3 /** 4 Person模型类 5 */ 6 @interface Person : NSObject 7 8 /** 9 姓名 10 */ 11 @property(nonatomic,copy) NSString *name; 12 13 /** 14 年龄 15 */ 16 @property(nonatomic,assign) NSInteger age; 17 18 /** 19 监听age属性发生变化 20 */ 21 - (void)kvoTest; 22 23 @end
1 #import "Person.h" 2 3 @implementation Person 4 5 - (void)kvoTest{ 6 7 [self willChangeValueForKey:@"age"]; 8 9 [self didChangeValueForKey:@"age"]; 10 } 11 12 @end
2、调用kvoTest方法监听对象属性age的变化:
1 #import "ViewController.h" 2 #import "Person.h" 3 4 @interface ViewController () 5 6 @end 7 8 @implementation ViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 13 Person *per = [[Person alloc] init]; 14 per.name = @"xiaoming"; 15 per.age = 18; 16 17 // 观察per对象的age属性的变化情况 18 [per addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:NULL]; 19 20 // kvoTest里面调用了willChangeValueForKey:和didChangeValueForKey 21 // 所以:不改变age的值,依旧会被监听到 22 [per kvoTest]; 23 24 // 这里po per的结果:<Person: 0x608000026960> 25 // 但是:po object_getClass(per)的结果:NSKVONotifying_Person 26 // 所以:苹果偷偷重写了派生类的class方法,但用运行时可以查看当前对象所属类型 27 28 // 移除KVO 29 [per removeObserver:self forKeyPath:@"age"]; 30 } 31 32 /** 33 KVO监听:当被监听对象的被监听属性变化后调用 34 35 @param keyPath 被监听的属性 36 @param object 被监听的对象 37 @param change 变化情况 38 @param context 携带的参数,此示例传入NULL即可 39 */ 40 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 41 { 42 NSLog(@"%@对象的%@属性发生变化了%@",object,keyPath,change); 43 44 // 打印结果: 45 // <Person: 0x608000032ca0>对象的age属性发生变化了{ 46 // kind = 1; 47 // new = 18; // 因为age的值根本没有发生变化,所是这里的new还是18 48 // } 49 } 50 51 52 @end