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];}
0 0
原创粉丝点击