KVO全称为Key-Value Observing, 即键值监听,用于指定对象属性值的改变。
问题一、iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
- 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey:
- 父类原来的setter
- didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
问题二、如何手动触发KVO?
- 手动调用willChangeValueForKey:和didChangeValueForKey:
问题三、直接修改成员变量会触发KVO么?
- 不会触发KVO 因为其内部是重写set方法来达到监听的
上面的问题 下面都会一一作出解释
定义如下一个Person类:
@interface Person : NSObject @property (assign, nonatomic) int age; @end
给在ViewController.h 里面给Person类的instance对象添加KVO
#import "ViewController.h" #import "MJPerson.h" @interface ViewController () @property (strong, nonatomic) MJPerson *person1; @property (strong, nonatomic) MJPerson *person2; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person1 = [[MJPerson alloc] init]; self.person1.age = 1; self.person2 = [[MJPerson alloc] init]; self.person2.age = 2; // 给person1对象添加KVO监听 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // self.person1.age = 21; // self.person2.age = 22; // NSKVONotifying_MJPerson是使用Runtime动态创建的一个类,是MJPerson的子类 // self.person1.isa == NSKVONotifying_MJPerson [self.person1 setAge:21]; // self.person2.isa = MJPerson [self.person2 setAge:22]; } - (void)dealloc { [self.person1 removeObserver:self forKeyPath:@"age"]; } // 当监听对象的属性值发生改变时,就会调用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context); } @end
当给Person的instance对象添加KVO监听后 内部发生如下的变化
1.Runtime会动态创建一个名为NSKVONotifying_Person的Person子类。
2.并将instance对象的isa指针指向这个子类Class。
3.并重写了属性的set方法 里面的实现如下
- 调用 willChangeValueForKey:
- [super setAge:age]
- 调用didChangeValueForKey: 里面会去通知监听器,属性值发生了改变
NSKVONotifying_Person类的setAge:方法的伪代码如下:
- (void)setAge:(int)age { //调用Foundation框架的_NSSet***ValueAndNotify方法,与具体参数类型有关 _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_Person 里面还重写了如下方法:
// 屏蔽内部实现,隐藏了NSKVONotifying_Person类的存在 - (Class)class { return [Person class]; } - (void)dealloc { // 收尾工作 } - (BOOL)_isKVOA { return YES; }
如何知道里面还有这些方法呢 我们可以用runtime 获取打印下
#import "ViewController.h" #import "MJPerson.h" #import <objc/runtime.h> @interface ViewController () @property (strong, nonatomic) MJPerson *person1; @property (strong, nonatomic) MJPerson *person2; @end //@implementation NSObject // //- (Class)class //{ // return object_getClass(self); //} // //@end // 反编译工具 - Hopper @implementation ViewController - (void)printMethodNamesOfClass:(Class)cls { unsigned int count; // 获得方法数组 Method *methodList = class_copyMethodList(cls, &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(@"%@ %@", cls, methodNames); } - (void)viewDidLoad { [super viewDidLoad]; self.person1 = [[MJPerson alloc] init]; self.person1.age = 1; self.person2 = [[MJPerson alloc] init]; self.person2.age = 2; // 给person1对象添加KVO监听 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"]; [self printMethodNamesOfClass:object_getClass(self.person1)]; [self printMethodNamesOfClass:object_getClass(self.person2)]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // [self.person1 setAge:21]; //手动触发KVO 调用willChangeValueForKey 和 didChangeValueForKey [self.person1 willChangeValueForKey:@"age"]; [self.person1 didChangeValueForKey:@"age"]; } - (void)dealloc { [self.person1 removeObserver:self forKeyPath:@"age"]; } // observeValueForKeyPath:ofObject:change:context: // 当监听对象的属性值发生改变时,就会调用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context); } @end
打印如下
由此可见,直接修改成员变量的值不会触发KVO。