OC——关于KVO

时间:2021-01-23 21:49:09

我们知道在WPF、Silverlight中都有一种双向绑定机制,如果数据模型修改了之后会立即反映到UI视图上,类似的还有如今比较流行的基于MVVM设计模式的前端框架,例如Knockout.js。其实在ObjC中原生就支持这种机制,它叫做Key Value Observing(简称KVO)。KVO其实是一种观察者模式,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。在ObjC中要实现KVO则必须实现NSKeyValueObServing协议,不过幸运的是NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO。

在ObjC中使用KVO操作常用的方法如下:

  • 注册指定Key路径的监听器: addObserver: forKeyPath: options:  context:
  • 删除指定Key路径的监听器: removeObserver: forKeyPathremoveObserver: forKeyPath: context:
  • 回调监听: observeValueForKeyPath: ofObject: change: context:

如何使用KVO呢?

首先,你要为你想观察的对象添加一个观察者代码如下:

[object addObserver: observer forKeyPath: @"frame" options: 0 context: nil];

调用方法是:

object : 被观察对象

observer: 观察对象

forKeyPath里面带上property的name,如UIView的frame、center等等

options: 有4个值,分别是:

NSKeyValueObservingOptionNew 把更改之后的值提供给处理方法

NSKeyValueObservingOptionOld 把更改之前的值提供给处理方法

NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。

NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。

注:例子里的0就代表不带任何参数进去

context: 可以带入一些参数,任何类型都可以,强制转就可以。

接下来观察到值的变化后该应该调用相关的方法来处理,这个方法是:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

其中:

keyPath: 对应forKeyPath

  object:  被观察的对象

  change:  对应options里的NSKeyValueObservingOptionNew、NSKeyValueObservingOptionOld等

  context: 对应context

示例代码

头文件

 #import <Foundation/Foundation.h>

 @interface Student : NSObject
@property (nonatomic)NSString *name; @property (nonatomic)NSString *courseName; @property (nonatomic)double age; - (void)changeCourseName:(NSString *)newCourseName;
@end

实现文件

 #import "Student.h"

 @implementation Student

 - (id)init
{
if(self = [super init])
{ [self addObserver:self
forKeyPath:@"courseName"
options:NSKeyValueObservingOptionNew
|NSKeyValueObservingOptionOld
context:nil];
[self addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew
|NSKeyValueObservingOptionOld
context:nil];
}
return self;
} - (void)changeCourseName:(NSString *)newCourseName{ _courseName = newCourseName; }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:@"courseName"])
{
NSLog(@"课程发生了改变");
NSLog(@"新课程是:%@ 老课程是:%@",[change objectForKey:@"new"],[change objectForKey:@"old"]); }
else if([keyPath isEqualToString:@"age"])
{
NSLog(@"年龄发生了改变");
NSLog(@"新年龄是:%@ 老年龄是:%@",[change objectForKey:@"new"],[change objectForKey:@"old"]);
}
}
#pragma mark 重写销毁方法
-(void)dealloc//注意一定要移除监听
{
//[super dealloc];//注意启用了ARC,此处不需要调用
[self removeObserver:self forKeyPath:@"courseName"];
[self removeObserver:self forKeyPath:@"age"]; }
@end

调用函数

#import <Foundation/Foundation.h>
#import "Student.h"
#import "pageView.h" int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *student = [[Student alloc] init];
student.courseName = @"math";
student.age = ;
student.courseName = @"ddd";
student.age = ; }
return ;
}

输出结果

2015-07-10 14:59:13.074 TestKVO[1649:1396305] 课程发生了改变

2015-07-10 14:59:13.076 TestKVO[1649:1396305] 新课程是:math  老课程是:<null>

2015-07-10 14:59:13.076 TestKVO[1649:1396305] 年龄发生了改变

2015-07-10 14:59:13.076 TestKVO[1649:1396305] 新年龄是:10  老年龄是:0

2015-07-10 14:59:13.076 TestKVO[1649:1396305] 课程发生了改变

2015-07-10 14:59:13.076 TestKVO[1649:1396305] 新课程是:ddd  老课程是:math

2015-07-10 14:59:13.077 TestKVO[1649:1396305] 年龄发生了改变

2015-07-10 14:59:13.077 TestKVO[1649:1396305] 新年龄是:15  老年龄是:10