KVO的内部实现原理
来源:互联网 发布:欧特克软件时限 编辑:程序博客网 时间:2024/05/17 16:44
一、kvo概述
kvo,全称Key-Value Observing,它提供了一种方法,当对象某个属性发生改变时,允许监听该属性值变化的对象可以接受到通知,然后通过kvo的方法响应一些操作。
在 Objective-C 中有两种使用键值观察的方式:手动或自动,此外还支持注册依赖键(即一个键依赖于其他键,其他键的变化也会作用到该键)。
二、运用键值观察
1、注册与解除注册
如果我们已经有了包含可供键值观察属性的类,那么就可以通过在该类的对象(被观察对象)上调用名为 NSKeyValueObserverRegistration 的 category 方法将观察者对象与被观察者对象注册与解除注册
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
这两个方法的定义在 Foundation/NSKeyValueObserving.h 中,NSObject,NSArray,NSSet均实现了以上方法,因此我们不仅可以观察普通对象,还可以观察数组或结合类对象。在该头文件中,我们还可以看到 NSObject 还实现了 NSKeyValueObserverNotification 的 category 方法。
- (void)willChangeValueForKey:(NSString *)key;- (void)didChangeValueForKey:(NSString *)key;
值得注意的是:不要忘记解除注册,否则会导致资源泄露。
2、设置属性
将观察者与被观察者注册好之后,就可以对观察者对象的属性进行操作,这些变更操作就会被通知给观察者对象。注意,只有遵循 KVO 方式来设置属性,观察者对象才会获取通知,也就是说遵循使用属性的 setter 方法,或通过 key-path 来设置:
[target setAge:30];[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];
3、处理变更通知
观察者需要实现名为 NSKeyValueObserving 的 category 方法来处理收到的变更通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
在这里,change 这个字典保存了变更信息,具体是哪些信息取决于注册时的 NSKeyValueObservingOptions。
二、kvo实现原理
kvo是如何实现通知对象的呢,其实这是通过Objective-C强大的runtime运行时机制实现的。当你第一次观察某个对象时,runtime会创建一个新的继承被监听类的子类。在这个新的类中,它会重写所有被观察的key,然后将对象的isa指针指向新创建的类。所以对象神奇的变成了新的子类的实例。这些被重写的方法中添加了调用通知观察者的方法的代码。当一个对象的一个属性改变时,会触发setKey方法,但这个方法被重写了,并且在内部添加了发送通知机制。
4、使用示例
观察者类:
// Observer.h@interface Observer : NSObject@end// Observer.m#import "Observer.h"#import <objc/runtime.h>#import "Target.h"@implementation Observer- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ if ([keyPath isEqualToString:@"age"]) { Class classInfo = (Class)context; NSString * className = [NSString stringWithCString:object_getClassName(classInfo) encoding:NSUTF8StringEncoding]; NSLog(@" >> class: %@, Age changed", className); NSLog(@" old age is %@", [change objectForKey:@"old"]); NSLog(@" new age is %@", [change objectForKey:@"new"]); } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; }}@end
注意:在实现处理变更通知方法 observeValueForKeyPath 时,要将不能处理的 key 转发给 super 的 observeValueForKeyPath 来处理。
使用示例:
Observer * observer = [[[Observer alloc] init] autorelease];Target * target = [[[Target alloc] init] autorelease];[target addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:[Target class]];[target setAge:30];//[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];[target removeObserver:observer forKeyPath:@"age"];
在这里 observer 观察 target 的 age 属性变化,运行结果如下:
>> class: Target, Age changed old age is 10 new age is 30
三、手动实现键值观察
@interface Target : NSObject{ int age;}// for manual KVO - age- (int) age;- (void) setAge:(int)theAge;@end@implementation Target- (id) init{ self = [super init]; if (nil != self) { age = 10; } return self;}// for manual KVO - age- (int) age{ return age;}- (void) setAge:(int)theAge{ [self willChangeValueForKey:@"age"]; age = theAge; [self didChangeValueForKey:@"age"];}+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key { if ([key isEqualToString:@"age"]) { return NO; } return [super automaticallyNotifiesObserversForKey:key];}@end
首先,需要手动实现属性的 setter 方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了;
其次,要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对该 key 不自动发送通知(返回 NO 即可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理。
四、自动实现键值观察
自动实现键值观察就非常简单了,只要使用了自动属性即可。
@interface Target : NSObject// for automatic KVO - age@property (nonatomic, readwrite) int age;@end@implementation Target@synthesize age; // for automatic KVO - age- (id) init{ self = [super init]; if (nil != self) { age = 10; } return self;}@end
五、kvo实现原理验证实验
- 1、新建
我们新建一个Single View Application工程。然后新建一个Person类和一个Dog类。 - 2、在Person.h中增加一个属性age。
#import <Foundation/Foundation.h>@interface Person : NSObject@property (nonatomic ,assign) int age;@end
- 3、在Dog.m中添加kvo监听方法
observeValueForKeyPath:ofObject:change:context:
#import "Dog.h"@implementation Dog//KVO- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ NSLog(@"%@监听到了%@对象的%@属性的值改变了:%@",self ,object ,keyPath ,change);}@end
- 4、在ViewController.m文件中添加如下代码
#import "ViewController.h"#import "Person.h"#import "Dog.h"@interface ViewController ()@property (nonatomic ,strong) Person* person;@property (nonatomic ,strong) Dog* dog;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; self.person = [[Person alloc]init]; self.dog = [[Dog alloc]init]; [self.person addObserver:self.dog forKeyPath:@"age" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.person.age ++;}@end
- 5、运行程序,观察person类变化
(1)、添加观察器之前:
(2)、添加观察器之后: - 6、总结
通过前后对比,我们发现当person对象被监听后,系统在运行时动态创建了一个继承自Person的子类NSKVONOtifying_Person类。然后KVO会在这个派生类中,重写基类中任何被观察属性的setter方法,在setter方法中实现真正的通知机制。
- (void)setAge:(int)age{[super setAge:age];[监听者 observeValueForKeyPath:@"age" ofObject:self change:@{} context:nil];}
- KVO内部的实现原理
- KVO的内部实现原理
- iOS ---- KVO的内部实现原理
- iOS ---- KVO的内部实现原理
- KVO的内部实现
- KVO的内部实现
- KVO的内部实现
- KVO的内部实现
- KVO的内部实现
- KVO 的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- (译)KVO的内部实现
- java读取文件提示系统找不到指定的文件
- 从ASCII码->Unicode->UTF-8历史变迁
- Android中so使用知识和问题总结以及插件开发过程中加载so的方案解析
- android数据库版本更新的实现
- VR设计人体工程学建议
- KVO的内部实现原理
- 常用Android开发工具下载集
- win7左ctrl和左alt键互换
- Linux 命令大全
- d3高级应用专题(二):在Canvas中绘制形状
- HMM求解观察序列概率的"前向算法"伪代码实现
- 文件权限
- gcc与g++的区别
- Android移动app架构设计浅谈