KVO,俗称键值监听,可以用于监听某个对象属性值的改变。
- 先简单的演示下KVO的使用方式
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
- (void)setAge:(int)age{
_age = age;
}
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Person *person1;
@property (nonatomic, strong) Person *person2;
@end
@implementation ViewController
- (void)dealloc{
[self.person1 removeObserver:self forKeyPath:@"age"];
[self.person2 removeObserver:self forKeyPath:@"age"];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[Person alloc] init];
self.person1.age = 20;
self.person2 = [[Person alloc] init];
self.person2.age = 19;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"height" options:options context:nil];
NSLog(@"person1添加KVO监听之后-%p",[self.person1 methodForSelector:@selector(setAge:)]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person1.age = 22;
self.person2.age = 23;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@ %@ %@",keyPath,object,change);
}
@end
点击手机屏幕,打击结果如下
2018-11-07 15:08:56.214085+0800 KVO[5647:212323] age <Person: 0x6000016739c0> {
kind = 1;
new = 22;
old = 20;
}
在touchesBegan方法内,相当于调用Person
类的setAge方法,但是person1
调用setAge方法后,又调用observeValueForKeyPath
方法、而person2
只是调用了setAge方法。由此可见,KVO的调用本质与setAge无关,而是与person1这个实例对象有关。我们在self.person12.age = 23;
处打断点,打印后发现person1继承自NSKVONotifying_Person
。而person2继承自Person。打印如下:
(lldb) p self.person1.isa
(Class) $0 = NSKVONotifying_Person
Fix-it applied, fixed expression was:
self.person1->isa


- 1、当观察对象A时,KVO机制利用runtime API动态的创建NSKVONotifying_A子类,并且让instance对象的isa指针指向这个全新的子类。
- 2、KVO为NSKVONotifying_A重写观察属性的setter方法,当修改instance的属性时,会调用Foundation的
_NSSetXXXValueAndNotify
方法。
底层调用情况
在self.person1.age = 22
处打断点,点击屏幕之后,调出lldb
,对person1添加KVO监听之后打印的地址值输出p IMP(0x105e51cf2)
,打印出结果
p IMP(0x105e51cf2)
(IMP) $0 = 0x0000000105e51cf2 (Foundation _NSSetIntValueAndNotify)
由此说明调用被监听的类中的setAge
方法,这个方法内部调用C语言函数_NSSetIntValueAndNotify
。
下面通过伪代码来说明子类NSKVONotifying_A内部的调用(不能运行)。
#import "NSKVONotifying_Person.h"
@implementation NSKVONotifying_Person
- (void)setAge:(int)age{
_NSSetIntValueAndNotify();
}
void _NSSetIntValueAndNotify(){
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
[self observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
@end
NSKVONotifying_Person内部重写setAge
方法。当age
属性值改变时:
- 1、调用
NSKVONotifying_Person
类中的setAge
方法,这个方法内部调用C语言函数_NSSetIntValueAndNotify
。 - 2、这个函数内部首先调用函数
willChangeValueForKey
方法,然后调用父类的setAge
方法,修改age属性值。然后再调用didChangeValueForKey
方法。 - 3、在
didChangeValueForKey
方法内,调用函数observeValueForKeyPath: ofObject: change: context:
监听到属性值的改变。
网友评论