KVO 本质与验证过程
-
第一部分:KVO 本质
(1)使用 runtime 动态创建 NSKVONotifying_xxx 类,该类是之前原始类的子类,并且该类有自己 setter 方法的实现
(2)原来的实例对象的 isa 指针指向新创建的类的类对象,这意味着:当实例对象的属性值发生改变时,实例对象的 isa 所指向的新的类对象中的 setter 方法会被调用
(3)新的类对象的 setAge 方法本质上是调用了 NSSetXXXValueAndNotifying 函数
(4)NSSetXXXValueAndNotifying 函数中,会先调用 willChangeValueForKey 方法,然后调用父类的 setter 方法将属性值改掉,再调用 didChangeValueForKey 方法
(5)didChangeValueForKey 方法中,会通知 observer 调用 observerValueForKeyPath 的监听方法
-
第二部分:验证过程
-
验证实例对象添加了 KVO 监听后,其 isa 指针指向一个新创建的类对象,代码如下:
#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" @interface ViewController () @property (nonatomic,strong) Person *person1; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.person1 = [[Person alloc] init]; self.person1.age = 10; //验证第一小点 Class cls1 = object_getClass(self.person1); NSLog(@"%@",NSStringFromClass(cls1)); //给 person 对象添加 KVO监听 [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; //验证第一小点 Class cls2 = object_getClass(self.person1); NSLog(@"%@",NSStringFromClass(cls2)); }
输出结果如下:
Person
NSKVONotifying_Person
由上面的打印结果可以看出,实例对象添加了 KVO 监听后 isa 指针指向的类对象确实发生了改变。
-
验证实例对象添加了 KVO 监听后其 isa 指针指向的新的类对象有自己的 setter 方法实现,代码如下:
#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" @interface ViewController () @property (nonatomic,strong) Person *person1; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.person1 = [[Person alloc] init]; self.person1.age = 10; IMP impl1 = [self.person1 methodForSelector:NSSelectorFromString(@"setAge:")]; NSLog(@"%p",impl1); //给 person 对象添加 KVO监听 [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; IMP impl2 = [self.person1 methodForSelector:NSSelectorFromString(@"setAge:")]; NSLog(@"%p",impl2); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.person1.age = 20; } //当监听对象对象的属性值发生改变时,就会调用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"监听到 %@ 的 %@ 属性值改变了 -- %@",object,keyPath,change); } - (void)dealloc { [self.person1 removeObserver:self forKeyPath:@"age"]; } @end
输出结果如下:
0x100f41388
0x180b6167c
-
在运行程序之前,我们可以在 viewDidLoad 方法的最后一行打下断点,然后运行程序,当控制台输出两个方法实现的地址值时,我们结合 lldb 动态调试器进行调试,如下所示:
通过 lldb 动态调试器将上面的地址强制转成 IMP 类型输出,调试过程如下所示:
0x100f41388
0x180b6167c
(lldb) p (IMP)0x100f41388
(IMP) $0 = 0x0000000100f41388 (KVO`-[Person setAge:] at Person.h:13)
(lldb) p (IMP)0x180b6167c
(IMP) $1 = 0x0000000180b6167c (Foundation`_NSSetIntValueAndNotify)
可以看到,添加 KVO 监听之前,属性的 setter 方法的地址对应的实现是 Person 类对象中的 setAge: 方法,并且该方法是通过“减号-中括号[ ]”的形式调用,很明显是 OC 的方法;而添加 KVO 监听之后, setter 方法的地址对应的实现就变成了Foundation 模块下的 _NSSetIntValueAndNotify,该方法很明显是通过 C 语言的形式来调用。至此第一部分 KVO 本质的前面 3 小点都得到了验证。
-
后面三点的验证,代码如下:
(1)Person 类的代码,我们在原始类(即 runtime 动态创建的子类 NSKVONotifying_xxx 的父类)中重写 willChangeValueForKey: 和 didChangeValueForKey: 两个方法,同时加入一些打印信息帮助我们分析,代码如下所示:
-
.h 文件
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic,assign) int age; @end
-
.m 文件
#import "Person.h" @implementation Person - (void)willChangeValueForKey:(NSString *)key { [super willChangeValueForKey:key]; NSLog(@"willChangeValueForKey"); } - (void)didChangeValueForKey:(NSString *)key { NSLog(@"didChangeValueForKey - begin"); [super didChangeValueForKey:key]; NSLog(@"didChangeValueForKey - end"); } @end
(2)ViewController 的代码:
-
.m 文件
#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" @interface ViewController () @property (nonatomic,strong) Person *person1; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.person1 = [[Person alloc] init]; self.person1.age = 10; //给 person 对象添加 KVO监听 [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.person1.age = 20; } //当监听对象对象的属性值发生改变时,就会调用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"监听到 %@ 的 %@ 属性值改变了 -- %@",object,keyPath,change); } - (void)dealloc { [self.person1 removeObserver:self forKeyPath:@"age"]; } @end
运行后,输出结果如下:
willChangeValueForKey
didChangeValueForKey - begin
监听到 <Person: 0x600002d34360> 的 age 属性值改变了 -- {
kind = 1;
new = 20;
old = 10;
}
didChangeValueForKey - end
至此, KVO 的本质已验证完成。
以上,感谢阅读!
-
-