-
iOS 中,实例对象添加了 KVO 监听之后,该实例对象调用 class 方法和执行 object_getClass 并传入该实例对象有什么区别?
结论:调用实例对象的 class 方法会返回该对象所属的类,而执行 object_getClass 并传入该实例对象会返回该对象的 isa 指针所指向的类。在普通的情况下,这两者是相同的。但是如果该实例对象所属的类是一个KVO子类,那么调用 class 方法会返回该实例对象原始的类,而执行 object_getClass 并传入该实例对象会返回 KVO 子类。这是因为 KVO 会通过运行时机制动态生成一个子类来代理原始类,从而实现 KVO 监听。
查看了苹果 runtime 的 NSObject 的 - (Class)class 官方源码,源码如下:- (Class)class { return object_getClass(self); }
- 可见,NSObject 的 class 实例方法的底层实现就是走了 object_getClass 方法。
- 通过代码测试,当实例对象添加了 KVO 监听之后,调用 class 会返回该实例对象原始的类。由此可见,添加了 KVO 监听的实例对象的 isa 指向一个通过 runtime 机制动态生成的子类类对象,该类对象的方法列表中重写了 class 方法,返回了原始类,从而屏蔽了内部实现,隐藏了 NSKVONotifying_XXX 类的存在。
-
怎么证明 Person 实例对象 p1 添加了 KVO 监听之后,程序动态创建一个 NSKVONotifying_Person 类是 Person 类的子类?
- 我们知道类对象的 superclass 指针指向它的父类的类对象,因此可以通过 superclass 指针来获取。示例代码如下:
#import "ViewController.h" #import <objc/runtime.h> struct gq_object_class { Class isa; Class superclass; }; @interface Person : NSObject @property (nonatomic,assign) int age; @end @implementation Person @end @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; struct gq_object_class *p1Cls = (__bridge struct gq_object_class *)object_getClass(self.person1); NSLog(@"%p",p1Cls->superclass); } //当监听对象对象的属性值发生改变时,就会调用 - (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
程序运行,当点击手机屏幕时,控制台输出地址值,通过 po 打印该地址值所指向的对象,如下所示:
0x100a61168
(lldb) po 0x100a61168
Person
-
iOS 用什么方式实现对一个对象的 KVO?(KVO 的本质是什么)
利用 RuntimeAPI 动态生成一个子类,并且让 instance 对象的 isa 指向这个全新的子类;
当修改 instance 对象的属性时,会调用属性的 setter 方法,并在该 setter 方法中调用 Foundation 的 _NSSetXXXValueAndNotify 函数。_NSSetXXXValueAndNotify函数内部主要做下面三件事情:
- 调用实例对象的 willChangeValueForKey: 方法;
- 调用父类原来的 setter 方法;
- 调用实例对象的 didChangeValueForKey: 方法
- didChangeValueForKey: 方法内部又会触发监听器(Observer)的监听方法(ObserveValueForKeyPath:ofObject:change:context:)
伪代码如下所示:
(1)NSKVONotifying_Person.h#import "MJPerson.h" @interface NSKVONotifying_Person : MJPerson @end
(2)NSKVONotifying_Person.m
#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 { // 通知监听器,某某属性值发生了改变 [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil]; } @end
-
如何手动触发 KVO ?
问题描述:KVO 的使用场景一般是当值被修改了,系统会自动帮我们调用 KVO 的监听方法(自动触发);现在想实现的是,在值没有被修改的情况下,KVO 的监听方法也能被我们调用(手动触发)。
示例代码如下:
#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" @interface ViewController () @property (nonatomic,strong) Person *person1; @property (nonatomic,strong) Person *person2; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.person1 = [[Person alloc] init]; self.person1.age = 10; self.person2 = [[Person alloc] init]; self.person2.age = 5; //给 person 对象添加 KVO监听 [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.person1 willChangeValueForKey:@"age"]; [self.person1 didChangeValueForKey:@"age"]; } //当监听对象对象的属性值发生改变时,就会调用 - (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
当程序运行后点击手机屏幕时,输出结果如下:
监听到 <Person: 0x600002284170> 的 age 属性值改变了 -- {
kind = 1;
new = 10;
old = 10;
}
网友评论