KVO底层分析
KVO的全称是 Key-Value Observing
,俗称“键值监听”,可以用于监听某个对象属性值的改变
基本使用
添加一个成员变量
@property (nonatomic, strong) YXCPerson *person;
- (void)viewDidLoad {
[super viewDidLoad];
_person = [YXCPerson new];
_person.name = @"Jack";
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[_person addObserver:self forKeyPath:@"name" options:options context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person.name = @"Tom";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@的%@属性发生了改变:%@", object, keyPath, change);
}
点击模拟器,输出结果
2019-08-12 11:22:27.251736+0800 KVO[18649:153721] <YXCPerson: 0x600002a1c410>的name属性发生了改变:{
kind = 1;
new = Tom;
old = Tom;
}
以上就是KVO的基本使用
为什么添加一个observe
就能监听到属性的改变?再新建一个Person
实例对象,不添加observe
,通过同样的方式修改name
属性的值,为什么添加了监听的实例对象就能监听?
接下来,再新建一个Person
实例对象,代码如下
- (void)viewDidLoad {
[super viewDidLoad];
_person1 = [YXCPerson new];
_person1.name = @"Jack";
self.person2 = [YXCPerson new];
self.person2.name = @"Tony";
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[_person1 addObserver:self forKeyPath:@"name" options:options context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person1.name = @"Tom";
self.person2.name = @"Kebi";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@的%@属性发生了改变:%@", object, keyPath, change);
}
在添加监听之前,之后输出Person1
和Person2
的类型
2019-08-12 11:32:55.876017+0800 KVO[19438:193756] 添加监听之前:self.person1 : YXCPerson, self.person2 : YXCPerson
2019-08-12 11:32:55.876745+0800 KVO[19438:193756] 添加监听之后:self.person1 : NSKVONotifying_YXCPerson, self.person2 : YXCPerson
再看看NSKVONotifying_YXCPerson
的父类是哪个类对象
NSLog(@"person1的父类是:%@", [object_getClass(self.person1) superclass]);
输出结果:
2019-08-12 11:37:31.339343+0800 KVO[19513:198711] person1的父类是:YXCPerson
在这里发现,添加了监听之后,Person1
的类型变成了NSKVONotifying_YXCPerson
,这是利用Runtime API 动态生成了额一个子类,并且让添加了监听的实例对象 isa
指向了这个全新的子类.
通过运行时获取 NSKVONotifying_YXCPerson
这个类的方法列表
- (void)printfMethodNameWithClass:(Class)class {
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(class, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", class, methodNames);
}
输出结果
NSKVONotifying_YXCPerson setName:, class, dealloc, _isKVOA,
通过输出结果,可以发现这个全新的子类有四个方法 setName:
class
dealloc
_isKVOA
在通过全新的子类的setName:
方法的地址,获取到实际上setName:
的方法
NSLog(@"%p", [self.person1 methodForSelector:@selector(setName:)]);
0x10a3beb5e
(lldb) p (IMP)0x10a3beb5e
(IMP) $0 = 0x000000010a3beb5e (Foundation`_NSSetObjectValueAndNotify)
这是发现 NSKVONotifying_YXCPerson
这个全新的类,在调用 setName:
这个方法的时候,使用的是 Fundation
框架的一个 _NSSetObjectValueAndNotify
C 语言方法
_NSSetObjectValueAndNotify
的内部实现
调用 willChangeValueForKey:
调用父类的 setter 实现方法
调用 didChangeValueForKey: (内部会调用observer的observeValueForKeyPath:ofObject:change:context方法)
也可以通过
[self.person1 willChangeValueForKey:@"name"];
[self.person1 didChangeValueForKey:@"name"];
手动触发监听
网友评论