###KVO 常见用法:
1、注册指定key 路径的监听器
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
observer : 观察者,也就是KVO 通知的订阅者,订阅者需要实现observeValueForKeyPath:ofObject:change: context: 方法;
keyPath : 描述将要观察的属性,相对于被观察者;
options : KVO 的一些属性设置
context : 上下文
2、删除指定key 路径的监听器
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
3、回调监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
keyPath:被监听的keyPath , 用来区分不同的KVO监听。
object: 被观察修改后的对象(可以通过object获得修改后的值)
change:保存信息改变的字典(可能有旧的值,新的值等)
context:上下文,用来区分不同的KVO监听
使用步骤:
1、注册,指定被观察的属性
2、实现回调方法
3、移除观察
@interface Person : NSObject
@property (nonatomic,assign) NSInteger age;
@end
@interface ViewController ()
@property (nonatomic,strong) Person *p;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.p = [[Person alloc] init];
//注册监听
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
self.p.age = 10;
}
//监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"age is changed = %@",[change valueForKey:NSKeyValueChangeNewKey]);
}else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:nil];
}
}
//移除监听
- (void)dealloc {
[self.p removeObserver:self forKeyPath:@"name" context:nil];
}
}
KVO 基本原理:
1、KVO 是基于runtime 机制实现的。
2、当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。
3、如果原类为Person ,那么生成的派生类名为NSKVONotifying_Person
4、每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷把isa指针指向动态创建的派生类,从而在给被监控属性赋值是执行的是派生类的setter方法。
5、键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:和 didChangeValueForKey: 在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context: 也会被调用。
KVO 的实现:
#import "Person.h"
@interface Person (KVO)
- (void)rc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
@end
@implementation Person (KVO)
- (void)rc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
//1、runtime 动态生成Person 类的子类(派生类)
//2、重写NSKVONotifying_Person 的属性setter方法,目的:监听属性改变
//3、修改对象的isa 指针
//修改isa 指针
object_setClass(self, NSClassFromString(@"NSKVONotifying_Person"));
//保存键值观察者对象 self -> p
//动态添加属性
objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
//实现子类(派生类)
#import "Person.h"
@interface NSKVONotifying_Person : Person
@end
@implementation NSKVONotifying_Person
//派生类重写setter 方法
- (void)setAge:(NSInteger)age {
[super setAge:age];
//调用观察者的observeValueForKeyPath
id observer = objc_getAssociatedObject(self, @"observer");
[observer observeValueForKeyPath:@"age" ofObject:self change:nil context:nil];
}
@end
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.p = [[Person alloc] init];
[self.p rc_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
self.p.age = 10;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"age is changed = %@",[change valueForKey:NSKeyValueChangeNewKey]);
}else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:nil];
}
}
参考链接:http://tech.glowing.com/cn/implement-kvo/