1. KVO的使用
- 假设当前有Person类,其中有一个int类型的属性age
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject @property (nonatomic, assign) int age; @end NS_ASSUME_NONNULL_END - 在控制器中生成Person对象person,addObserver给person的age添加KVO监听,点击屏幕时改变person的age值
#import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic, strong) Person *person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [Person new]; self.person.age = 20; NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person addObserver:self forKeyPath:@"age" options:options context:nil]; } -(void)dealloc{ [self.person removeObserver:self forKeyPath:@"age"]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.age = 10; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"监听到%@的%@属性值改变了 - %@", object, keyPath, change); } @end - 点击屏幕,输出结果:
监听到<Person: 0x600001364530>的age属性值改变了 - { kind = 1; new = 10; old = 20; }
2. KVO的本质
-
在给person对象添加监听后,查看对象的isa,发现了这个对象的isa指向了名为
NSKVONotifying_Person的类对象。由此推断,系统为Person类生成了名为NSKVONotifying_Person的子类,专门用于监听值变化。 -
内存结构分析
-
未使用KVO监听的对象,内存结构如下:
-
使用了KVO监听的对象,内存结构如下:
-
NSKVONotifying_Person的猜想伪代码如下:#import "NSKVONotifying_MJPerson.h" @implementation NSKVONotifying_MJPerson - (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]; } // 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在。 - (Class)class{ return [MJPerson class]; } - (void)dealloc{ // 收尾工作 } - (BOOL)_isKVOA{ return YES; } @end -
为什么是
_NSSetIntValueAndNotify?进行如下验证:self.person = [Person new]; NSLog(@"person添加KVO监听之前 - %p", [self.person methodForSelector:@selector(setAge:)]); NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person addObserver:self forKeyPath:@"age" options:options context:nil]; NSLog(@"person添加KVO监听之后 - %p", [self.person methodForSelector:@selector(setAge:)]);打印结果如下:
-
使用object_getClass(),可以获取到真实的类
self.person = [Person new]; NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person addObserver:self forKeyPath:@"age" options:options context:nil]; NSLog(@"%@", object_getClass(self.person)); NSLog(@"%@", [self.person class]);输出结果如下:
3. 总结,KVO的本质:
- 利用RuntimeAPI动态生成了一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey:
- 父类原来的setter
- didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)