什么是KVO?
KVO是Key-Value-Observing的缩写,通过KVO这种机制对象可以通过它得到其他对象的某个属性的变更通知。这种机制在MVC模式下显得更为重要,KVO可以让视图对象经过控制器观察模型对象的变更从而做出更新等操作。
KVO这一机制是基于NSKeyValueObserving协议的,Cocoa通过这个协议为所有遵循协议的对象提供了自动观察属性变化的能力。在NSObject中已经为我们实现了这一协议,所以我们不必去实现这个协议。
下图形象的表示了KVO的一种工作流程:
为什么要使用KVO?
有的朋友可能会有疑问,为什么要使用KVO呢?KVO能实现的我使用Setter方法同样能实现啊。其实不然KVO存在还是有它的价值的,那么接下来我们细数一下KVO的独特价值吧:
1.我们创建一两个setter方法感觉没什么,但是如果要观察的属性非常多,那么还能一一重写setter方法来实现吗?想必大家心里已有了答案,但是利用KVO则能很好的解决上述问题。
2.我们自定义的类是很容易改写setter方法的,但是如果你是用一个已经编译好了的类库时要监控其中一个属性时怎么办?难道还要去重写setter方法?如果使用KVO则很轻松解决问题。
3.使用KVO能够方便的记录变化前的值和变化后的值,不适用KVO你还要自己来解决这些问题。
4.KVO让你的代码看起来更加简洁清晰易于维护。
如何使用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
实战代码
我们写一个学生类写一个pageview类来观察学生类中某些属性的变化并作出相关响应。
1 Student.h 2 #import <Foundation/Foundation.h> 3 4 @interface Student : NSObject 5 6 @property (nonatomic)NSString *name; 7 8 @property (nonatomic)NSString *courseName; 9 10 @property (nonatomic)double age; 11 12 - (void)changeCourseName:(NSString *)newCourseName; 13 14 @end 15 16 Student.m 17 #import "Student.h" 18 19 @implementation Student 20 21 - (void)changeCourseName:(NSString *)newCourseName{ 22 _courseName = newCourseName; 23 } 24 25 @end 26 27 pageView.h 28 #import <Foundation/Foundation.h> 29 #import "Student.h" 30 31 @interface pageView : NSObject 32 - (id)init:(Student*)stu; 33 @end 34 35 pageView.m 36 #import "pageView.h" 37 38 @implementation pageView{ 39 Student *student; 40 } 41 42 - (id)init:(Student*)stu{ 43 if(self = [super init]){ 44 student = stu; 45 [stu addObserver:self 46 forKeyPath:@"courseName" 47 options:NSKeyValueObservingOptionNew 48 |NSKeyValueObservingOptionOld 49 context:nil]; 50 [stu addObserver:self 51 forKeyPath:@"age" 52 options:NSKeyValueObservingOptionNew 53 |NSKeyValueObservingOptionOld 54 context:nil]; 55 } 56 return self; 57 } 58 59 - (void)dealloc{ 60 [student removeObserver:self forKeyPath:@"courseName"]; 61 } 62 63 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ 64 if([keyPath isEqualToString:@"courseName"]){ 65 NSLog(@"课程发生了改变"); 66 NSLog(@"新课程是:%@ 老课程是:%@",[change objectForKey:@"new"],[change objectForKey:@"old"]); 67 }else if([keyPath isEqualToString:@"age"]){ 68 NSLog(@"课程发生了改变"); 69 NSLog(@"新课程是:%@ 老课程是:%@",[change objectForKey:@"new"],[change objectForKey:@"old"]); 70 } 71 } 72 73 @end 74 75 main.m 76 #import <Foundation/Foundation.h> 77 #import "Student.h" 78 #import "pageView.h" 79 80 int main(int argc, const char * argv[]) 81 { 82 83 @autoreleasepool { 84 85 Student *student = [[Student alloc] init]; 86 student.courseName = @"math"; 87 student.age = 10; 88 pageView *view = [[pageView alloc] init:student]; 89 student.courseName = @"ddd"; 90 student.age = 15; 91 NSLog(@"%@",student.courseName); 92 93 } 94 return 0; 95 }
本文摘自,感谢作者的解释:http://www.androiddev.net/kvo/
最后对自己不懂的地方加一段补充如下,为什么必须触发属性的setter方法才能激发KVO响应函数:
解释如下(网上解释):
KVO 也许是iOS中“最神奇”的部分了,因为你不需要在被观察对象中添加任何代码,就可以实现对被观察对象属性改变的通知。KVO究竟是怎么实现的?
KVO是通过Objective-C的runtime来实现的。当你第一次要对一个对象进行观察时,runtime会为你创建一个被观察对象class的subclass。在这个新创建的subclass中,KVO会复写所要观察属性的setter方法,然后转换被观察对象的isa指针,指向新创建的subclass,所以,你想要观察的对象,变成了KVO在runtime时创建的subclass。因为Apple不想让这种机制暴露,所以还会复写要观察对象的class方法,所以,当你调用class来判断该对象的class时,还会显示原对象的class类型,而不是subclass的类型