使用方式
通过以下例子来总结使用方式
// ViewController.h
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
self.person = person;
person.age = 10;
person.age = 20;
// 添加观察者为当前控制器,对age进行观察
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
[person addObserver:self forKeyPath:@"age" options:options context:nil];
person.age = 30;
}
- (void)dealloc {
// 移除观察者,防止内存泄露
[self.person removeObserver:self forKeyPath:@"age"];
}
// 当被观察的数据变化时,提醒观察者
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"改变的值---- %@", change);
}
@end
result:
改变的值---- {
kind = 1;
new = 30;
old = 20;
}
使用方式:
1、添加观察者
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2、观察者实现对应的观察方法(数据变化时进行处理)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)contex
3、移除观察者(防止内存泄露)
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
本质分析
原来上面的例子Person中person.age = 10、person.age = 20与person.age = 30在写法上没有任何区别,都是调用了setter方法。而唯有添加了观察者后的,person.age = 30才有反应。
从编译时看不出来变化,说明在KVP是在runtime时动了手脚。
在添加观察者前后分别打断点,可以看到isa指针变化了。
实际上加了KVO后,对象的isa指针会重新指向 “NSKVONotifying_对象” (实际上市该类对象的子类)
而在 “NSKVONotifying_对象”该类对象中 重新了setter方法。
重写方法中实际调用过程为
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
而在didChangeValueForKey:方法中,会调用观察者的观察方法即[obser observeValueForKeyPath:@"age" ofObject:self change:{} content:nil];
为了证明这个过程,操作如下
Person.m文件中以下方法
// Person.m
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"%s", __func__);
[super willChangeValueForKey:key];
}
- (void)setAge:(int)age {
NSLog(@"%s", __func__);
_age = age;
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"%s", __func__);
[super didChangeValueForKey:key];
NSLog(@"%s", __func__);
}
ViewController.m文件中以下方法
// ViewController.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%s", __func__);
}
打印结果如下
-[Person setAge:]
-[Person setAge:]
// KVO 的过程
-[Person willChangeValueForKey:]
-[Person setAge:]
-[Person didChangeValueForKey:]
-[ViewController observeValueForKeyPath:ofObject:change:context:]
-[Person didChangeValueForKey:]
上述表明KVO调用过程实际就是
[self willChangeValueForKey:@"观察的值"];
[super setAge:值];
[self didChangeValueForKey:@"观察的值"];
而在didChangeValueForKey:中又调用了观察者的观察方法即observeValueForKeyPath:ofObject:change:context方法。
问题
1、KVO本质是什么?
本质是改变了对象isa指针的指向并重写了setter方法,指向一个NSKVONotifying_对象的子类对象。
重写setter方法,如下
1)willChangeValueForKey:
2)父类原来的setter
3)didChangeValueForKey:
内部会触发观察者(Oberser)的观察方法( observeValueForKeyPath:ofObject:change:context:)
2、如何手动触发KVO?
手动调用调用willChangeValueForKey:和didChangeValueForKey:方法。