OC中的KVO原理
KVO
KVO的全称是Key-Value Observing,俗称"键值监听",一般用于监听某个对象属性值的改变
KVO代码实现
#import "ViewController.h" #import <objc/runtime.h> @interface LBPerson : NSObject @property (nonatomic, assign) int age; @end @implementation LBPerson @end @interface ViewController () @property (nonatomic, strong) LBPerson *person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LBPerson *person = [[LBPerson alloc] init]; self.person = person; [person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"test"]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.person.age = 10; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context); } - (void)dealloc { [self.person removeObserver:self forKeyPath:@"age"]; } @end
打印结果如下:
2020-11-23 22:14:22.171102+0800 KVOTest[60482:2523973] 监听到<LBPerson: 0x600000ccc670>的age属性值改变了 - { kind = 1; new = 10; old = 0; } - test
对比对对象的属性添加监听后的打印变化
新增如下代码:
- (void)viewDidLoad { [super viewDidLoad]; LBPerson *person = [[LBPerson alloc] init]; self.person = person; LBPerson *person1 = [[LBPerson alloc] init]; NSLog(@"Person类的对象添加监听之后- %@ %@", object_getClass(self.person), object_getClass(person1)); NSLog(@"Person类的对象添加监听之后- %p %p", [self.person methodForSelector:@selector(setAge:)], [person1 methodForSelector:@selector(setAge:)]); NSLog(@"元类对象--- %@ %@",object_getClass(object_getClass(self.person)),object_getClass(object_getClass(person1))); [person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"test"];
NSLog(@"Person类的对象添加监听之后- %@ %@", object_getClass(self.person), object_getClass(person1)); NSLog(@"Person类的对象添加监听之后- %p %p", [self.person methodForSelector:@selector(setAge:)], [person1 methodForSelector:@selector(setAge:)]); NSLog(@"元类对象--- %@ %@",object_getClass(object_getClass(self.person)),object_getClass(object_getClass(person1))); }
打印结果如下:
2020-11-23 22:18:47.625352+0800 KVOTest[60504:2526933] Person类的对象添加监听之后- LBPerson LBPerson 2020-11-23 22:18:47.625659+0800 KVOTest[60504:2526933] Person类的对象添加监听之后- 0x1075a9670 0x1075a9670 2020-11-23 22:18:47.625850+0800 KVOTest[60504:2526933] 元类对象--- LBPerson LBPerson 2020-11-23 22:18:47.626450+0800 KVOTest[60504:2526933] Person类的对象添加监听之后- NSKVONotifying_LBPerson LBPerson 2020-11-23 22:18:47.626652+0800 KVOTest[60504:2526933] Person类的对象添加监听之后- 0x7fff207d2ce3 0x1075a9670 2020-11-23 22:18:47.626801+0800 KVOTest[60504:2526933] 元类对象--- NSKVONotifying_LBPerson LBPerson
对比打印结果,对象的某属性被监听之后,系统自动帮忙创建了NSKVONotifying_类名的一个新类,对于两个对象的setAge的打印结果同样可以得到证明,添加监听之前
打印setAge的方法地址是相同的,监听之后,地址不太相同。
下面我在LBPerson类的下面。创建一个继承于LBPerson的NSKVONotifying_LBPerson,代码如下
@interface LBPerson : NSObject @property (nonatomic, assign) int age; @end @implementation LBPerson @end @interface NSKVONotifying_LBPerson : LBPerson @end @implementation NSKVONotifying_LBPerson @end
再运行之后打印结果如下:
2020-11-23 22:27:39.146253+0800 KVOTest[60558:2532017] [general] KVO failed to allocate class pair for name NSKVONotifying_LBPerson, automatic key-value observing will not work for this class
也可以正式再添加kvo之后,通过runtime创建了一个分类
监听属性的方法调用顺序
在LBPerson类中添加重写如下方法,添加这些方法的目的是让系统生成的中间类能够继承这些方法,打印出我们想要的数据:
@interface LBPerson : NSObject @property (nonatomic, assign) int age; @end @implementation LBPerson - (void)setAge:(int)age { _age = age; NSLog(@"setAge:"); } - (void)willChangeValueForKey:(NSString *)key { [super willChangeValueForKey:key]; NSLog(@"willChangeValueForKey"); } - (void)didChangeValueForKey:(NSString *)key { NSLog(@"didChangeValueForKey Start"); [super didChangeValueForKey:key]; NSLog(@"didChangeValueForKey End"); } @end
打印结果如下:
2020-11-23 22:35:27.686978+0800 KVOTest[60592:2536626] willChangeValueForKey 2020-11-23 22:35:27.687163+0800 KVOTest[60592:2536626] setAge: 2020-11-23 22:35:27.687345+0800 KVOTest[60592:2536626] didChangeValueForKey Start 2020-11-23 22:35:27.687813+0800 KVOTest[60592:2536626] 监听到<LBPerson: 0x6000007cc4e0>的age属性值改变了 - { kind = 1; new = 10; old = 0; } - test 2020-11-23 22:35:27.688023+0800 KVOTest[60592:2536626] didChangeValueForKey End
从上述打印得到的方法调用顺序:
willChangeValueForKey
setAge:
didChangeValueForKey
observeValueForKeyPath