先来看一下Objective-C中KVO的用法
#import "ViewController.h"
#import "BDFPerson.h"
@interface ViewController ()
@property (strong, nonatomic) BDFPerson *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[BDFPerson alloc]init];
self.person.age = 1;
self.person.height = 11;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
[self.person addObserver:self forKeyPath:@"height" options:options context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person.age = 2;
self.person.height = 22;
}
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"age"];
[self.person removeObserver:self forKeyPath:@"height"];
}
@end
打印结果如下:
2019-12-30 15:45:09.057647+0800 KVODemo[19599:898383] 监听到<BDFPerson: 0x6000008ca4a0>的age属性值改变了 - {
kind = 1;
new = 11;
old = 1;
} - (null)
2019-12-30 15:45:09.058006+0800 KVODemo[19599:898383] 监听到<BDFPerson: 0x6000008ca4a0>的height属性值改变了 - {
kind = 1;
new = 22;
old = 2;
} - (null)
这里我们发现,点击屏幕,调用 self.person.age = 2; self.person.height = 22;
方法, addObserver这个监听器可以在observeValueForKeyPath代理方法中监听到值的变化,那么这是为什么呢?
这里我们来分析一下:
- 首先,代码
self.person.age = 2;
self.person.height = 22;
等同于
[self.person setAge:2];
[self.person setAge:22];
那么也就是说,他们都是调用set方法,set方法是相同的,所以没有差异,差异可能就会存在 person 对象本身。
我们来看下对象 person 的 isa
打印结果如下:
(lldb) po self.person.isa
NSKVONotifying_BDFPerson
Fix-it applied, fixed expression was:
self.person1->isa
这里我们发现 person的isa是NSKVONotifying
对象。然而正常的alloc init 的示例对象,它的isa是类对象。
下面这个是没有添加监听,正常创建的对象的isa.
(lldb) po self.person.isa
BDFPerson
Fix-it applied, fixed expression was:
self.person2->isa
通过对比我们发现,添加了KVO监听后的isa对象是NSKVONotifying_BDFPerson,但是这个类并不是我们创建的,它是怎么来的呢?既然不是我们创建的,我们可以猜测,它肯定是通过OC的Runtime机制,在程序动态运行的时候添加的。
NSKVONotifying_BDFPerson类窥探方法:
1.在添加监听前后,我们可以通过object_getClass(self.person)
方法,来查看控制台输出变化。NSLog(@"----personal1添加KVO监听之前 - %@ ", object_getClass(self.person1)); // 给person1对象添加KVO监听 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil]; [self.person1 addObserver:self forKeyPath:@"height" options:options context:nil]; NSLog(@"----personal1添加KVO监听之后 - %@ ", object_getClass(self.person),
输出结果如下:
2020-01-01 11:27:34.124882+0800 KVODemo[3029:42894] ----personal1添加KVO监听之前 - BDFPerson 2020-01-01 11:27:46.480695+0800 KVODemo[3029:42894] ----personal1添加KVO监听之后 - NSKVONotifying_BDFPerson
2.通过打印IMP指针的内存地址,来查看方法。 (命令:po (IMP)内存地址)
NSLog(@"----person1添加KVO监听之前 - %p", [self.person1 methodForSelector:@selector(setAge:)]); // 给person1对象添加KVO监听 NSKeyValueObservingOptions options = >NSKeyValueObservingOptionNew | >NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil]; [self.person1 addObserver:self forKeyPath:@"height" options:options context:nil]; NSLog(@"----person1添加KVO监听之后 - %p %p", [self.person1 methodForSelector:@selector(setAge:)]);
person1的setAge:方法的内存地址输出结果如下:
2020-01-01 11:38:31.687585+0800 KVODemo[3584:69032] ----person1添加KVO监听之前 - 0x1011f2160 2020-01-01 11:38:44.173528+0800 KVODemo[3584:69032] ----person1添加KVO监听之后 - 0x7fff257023ea
进入LLDB断点调试模式,通过po输出IMP结果如下:
(lldb) po (IMP)0x1011f2160 (KVODemo`-[BDFPerson setAge:] at BDFPerson.m:12) (lldb) po (IMP)0x7fff257023ea (Foundation`_NSSetIntValueAndNotify)
系统在程序运行时,动态给我们添加的NSKVONotifying_BDFPerson这个类,它其实是我们BDFPerson的一个子类。所以,如果是NSKVONotifying_BDFPerson类对象,会执行子类自己里面的setAge:方法,子类里面的这个方法和父类BDFPerson里面的setAge:是不一样的。子类里面的setAge:方法会执行Foundation框架里面的_NSSetIntValueAndNotify
方法。
_NSSetIntValueAndNotify
函数里面的逻辑如下:
-(void)setAge:(int)age {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}-(void)didChangeValueForKey:(NSString *)key {
// 通知监听器
[observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
所以,本质就是KVO的isa不一样,所以执行的set方法的实现也就不一样。
总结:如果是正常创建的对象person1 ,它的isa是Person它本身。
如果是添加了监听的对象person2,它的isa是 NSKVONotifying_
1.正常创建的Person类 调用setAge:方法。
2.添加监听的Peson类,实际上是NSKVONotifying_类,它里面的set方法如下:
-(void)setAge:(int)age {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
所以,添加监听的对象和正常创建的对了,set方法内部实现的原理不一样。所以也就能解释为何添加了监听的对象能够监听值的变化问题了。
网友评论