KVO实现原理
什么是KVO
KVO全称KeyValueObserving,俗称键值监听,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
KVO实现原理
-
KVO是通过isa-swizzling技术实现的(这句话是整个KVO实现的重点)。
-
在运行时根据原类创建一个中间类(假如这个类名为Person,那么这个中间类就为NSKVONotifying_Person),这个中间类是原类的子类,并动态修改当前对象的isa指向中间类。当修改 instance 对象的属性时,会调用 Foundation框架的 _NSSetXXXValueAndNotify 函数 ,该函数里面会先调用 willChangeValueForKey: 然后调用父类原来的 setter 方法修改值,最后是 didChangeValueForKey:。didChangeValueForKey 内部会触发监听器(Oberser)的监听方法observeValueForKeyPath:ofObject:change:context:
-
并且将class方法重写,返回原类的Class。
实践
-
首先创建一个Person类,继承自NSObject。
Person.h (部分代码)
1 2 3 4 5
@interface Person : NSObject @property (nonatomic, assign) int age; @end
-
在控制器中初始化一个Person实例。
ViewController.m(部分代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
@interface ViewController () @property (nonatomic, strong) Person *man; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. Person *man = [Person new]; _man = man; //断点1 [man addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ man.age = 10; }); } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"%@", object); //断点2 NSLog(@"%@", keyPath); NSLog(@"%@", change); }
在断点1和2分别打上断点,然后在lldb中输入:
1
p _man->isa
可以看到,分别打印了:
断点1:
1 2
(lldb) p _man->isa (Class) $1 = Person
断点2:
1 2
(lldb) p _man->isa (Class) $2 = NSKVONotifying_Person
这说明了Runtime的isa-swizzling动态的生成了Person的子类:NSKVONotifying_Person,并且把_man的isa指向了NSKVONotifying_Person。
-
松开断点,可以看到控制台打印:
1 2 3 4 5 6 7
2020-05-13 17:00:13.258194+0800 KVO[37184:1734037] <Person: 0x6000016e2220> 2020-05-13 17:00:13.258402+0800 KVO[37184:1734037] age 2020-05-13 17:00:13.258636+0800 KVO[37184:1734037] { kind = 1; new = 10; old = 0; }
-
如果需要手动触发KVO,可以在Person.h加入一个成员变量:
1 2 3 4 5 6 7 8
@interface Person : NSObject { @public NSString *_name; } @property (nonatomic, assign) int age; @end
在ViewController.m中添加观察者:
1
[man addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
此时,修改_man->_name,
1 2 3 4
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ man.age = 10; man->_name = @"Tom"; });
打印后可以发现,并没有监听到name的变化。
手动触发:
1 2 3 4 5 6 7 8 9 10 11
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ man.age = 10; //不会触发KVO //man->_name = @"Tom"; //手动触发 [man willChangeValueForKey:@"name"]; man->_name = @"Tom"; [man didChangeValueForKey:@"name"]; });
-
还可以通过KVC触发:
1
[man setValue:@"Tome" forKey:@"name"];
-
最后,要记得移除观察者,如果重复移除,将会导致崩溃。
1 2 3 4
- (void)dealloc { [_man removeObserver:self forKeyPath:@"age"]; }
参考资料: