我们知道在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: forKeyPath、removeObserver: 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
示例代码
头文件
1 #import <Foundation/Foundation.h> 2 3 @interface Student : NSObject 4 @property (nonatomic)NSString *name; 5 6 @property (nonatomic)NSString *courseName; 7 8 @property (nonatomic)double age; 9 10 - (void)changeCourseName:(NSString *)newCourseName; 11 @end
实现文件
1 #import "Student.h" 2 3 @implementation Student 4 5 - (id)init 6 { 7 if(self = [super init]) 8 { 9 10 [self addObserver:self 11 forKeyPath:@"courseName" 12 options:NSKeyValueObservingOptionNew 13 |NSKeyValueObservingOptionOld 14 context:nil]; 15 [self addObserver:self 16 forKeyPath:@"age" 17 options:NSKeyValueObservingOptionNew 18 |NSKeyValueObservingOptionOld 19 context:nil]; 20 } 21 return self; 22 } 23 24 - (void)changeCourseName:(NSString *)newCourseName{ 25 26 _courseName = newCourseName; 27 28 } 29 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 30 { 31 if([keyPath isEqualToString:@"courseName"]) 32 { 33 NSLog(@"课程发生了改变"); 34 NSLog(@"新课程是:%@ 老课程是:%@",[change objectForKey:@"new"],[change objectForKey:@"old"]); 35 36 } 37 else if([keyPath isEqualToString:@"age"]) 38 { 39 NSLog(@"年龄发生了改变"); 40 NSLog(@"新年龄是:%@ 老年龄是:%@",[change objectForKey:@"new"],[change objectForKey:@"old"]); 41 } 42 } 43 #pragma mark 重写销毁方法 44 -(void)dealloc//注意一定要移除监听 45 { 46 //[super dealloc];//注意启用了ARC,此处不需要调用 47 [self removeObserver:self forKeyPath:@"courseName"]; 48 [self removeObserver:self forKeyPath:@"age"]; 49 50 } 51 @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 = 10; student.courseName = @"ddd"; student.age = 15; } return 0; }
输出结果
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