浅析 KVO 内部实现
来源:互联网 发布:淘宝助理 假死 编辑:程序博客网 时间:2024/06/05 12:40
KVO 全称是Key Value Observing,翻译成键值观察。提供了一种当其它对象属性被修改的时候能通知当前对象的机制。
KVO 的基本使用:
(1)注册指定Key路径的监听器:
/** 参数 * addObserver: 监听对象 * forKeyPath: 监听属性Key * options: 监听可选项 * NSKeyValueObservingOptionNew: 监听改变后的新值 * NSKeyValueObservingOptionOld: 监听改变后的旧值 * context: 传入的上下文 */- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
(2)删除指定Key路径的监听器:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
(3)回调监听:
- (void)observeValueForKeyPath:(NSString *)keyPath //监听的属性值 ofObject:(id)object //监听的对象 change:(NSDictionary<NSString *,id> *)change //值的改变(由options参数决定传入新值或者旧值) context:(void *)context //传入的上下文内容
值得注意的是:不要忘记解除注册,否则会导致资源泄露。
设置属性
将观察者与被观察者注册好之后,就可以对观察者对象的属性进行操作,这些变更操作就会被通知给观察者对象。注意,只有遵循 KVO 方式来设置属性,观察者对象才会获取通知,也就是说遵循使用属性的 setter 方法,或通过 key-path 来设置:
target.age = 30;[target setAge:30]; [target setValue:[NSNumber numberWithInt:30] forKey:@"age"];
下面看一个小 Demo:
我们设想一个场景,当自己住酒店的时候,当酒店给我换房间的时候,我们要得到提醒,才能找对自己的房间。我们依次为例:
//ViewController#import "ViewController.h"#import "Person.h"#import "Room.h"@interface ViewController ()@property (nonatomic, strong) Person *person;@property (nonatomic, strong) Room *room;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; self.person = [[Person alloc] init]; self.room = [[Room alloc] init]; //设置房间的号码 self.room.no = 10; //Person 监听 Room 编号的变化 [self.room addObserver:self.person forKeyPath:@"no" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //房间号变为20 self.room.no = 20;}@end//Person.m 文件#import "Person.h"@implementation Person- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"no"]) { NSLog(@"Person 检测到 Room 的属性: %@ 值改变: %@", keyPath, change); }}//移除观察者对象,防止内存泄漏- (void)dealloc{ [self.room removeObserver:self forKeyPath:@"no"];}@end
运行,我们得到:
KVO 的内部实现
下面我们分析下,KVO 的内部实现:
1> KVO 是基于 runtime 的 isa-swizzing 机制实现的;
2> 当类 A 的对象第一次被观察的时候,系统会在运行期动态创建类A 的派生类。系统命名为NSKVONotifying_A。
3> 在派生类 NSKVONotifying_A 中重写类 A 的setter方法,NSKVONotifying_A类在被重写的setter方法中实现通知机制。
4> 其中键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,observeValueForKey:ofObject:change:context: 会被调用,继而 didChangeValueForKey: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了(后面介绍)。
5> 类 NSKVONotifying_A会重写 class方法,将自己伪装成类A。类 NSKVONotifying_A 还会重写 deallo 方法来释放资源。
系统将所有指向类 A 对象的isa指针指向类 NSKVONotifying_A 的对象。
为了证明上述过程:我们第一步注释掉ViewController 添加观察者的代码,在运行的时候,查看类 Room 的 isa 指针的值:
当将添加观察者处的代码打开,我们观察到,在运行的时候,Room 的 isa指针指向了NSKVONotifying_Room 类(派生类)
KVO 手动实现
在 Room.m 文件中实现:
/**首先,需要手动实现属性的 setter 方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了;其次,要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对该 key 不自动发送通知(返回 NO 即可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理。*/#import "Room.h"@implementation Room- (void)setNo:(int)no{ [self willChangeValueForKey:@"no"]; _no = no; [self didChangeValueForKey:@"no"];}+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{ if ([key isEqualToString:@"no"]) { return NO; } return [super automaticallyNotifiesObserversForKey:key];}@end
当我们再次运行观察,发现 Room 的 isa 指针指向Room类:
参考:
http://www.cppblog.com/kesalin/archive/2012/11/17/kvo.html
申明
以上观点,属于个人的理解,如果错误之处,欢迎拍砖。
- 浅析 KVO 内部实现
- KVO的内部实现
- KVO的内部实现
- KVO的内部实现
- KVO的内部实现
- KVO的内部实现
- KVO 的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- KVO内部的实现原理
- KVO的内部实现原理
- (译)KVO的内部实现[转载]
- JSPatch初体验
- 阿里巴巴2017实习生笔试题(一)
- POJ - 3294 Life Forms
- Js 原型和继承
- php与设计模式之装饰模式
- 浅析 KVO 内部实现
- Java 基础分析
- 奇葩网络问题归总
- 【javase复习】## day6 线程 ##
- Ubuntu apt-get update 时提示由于没有公钥,无法验证下列签名!
- 【HDU2586】How far away?-LCA算法模板题
- linux命令tree
- spring的定时任务
- NOIP 2015 信息传递