KVO是Key-Value-Observing的缩写,通过KVO这种机制对象可以通过它得到其他对象的某个属性的变更通知。这种机制在MVC模式下显得更为重要,KVO可以让视图对象经过控制器观察模型对象的变更从而做出更新等操作。
KVO这一机制是基于NSKeyValueObserving协议的,Cocoa通过这个协议为所有遵循协议的对象提供了自动观察属性变化的能力。在NSObject中已经为我们实现了这一协议,所以我们不必去实现这个协议。
下图形象的表示了KVO的一种工作流程:
有的朋友可能会有疑问,为什么要使用KVO呢?KVO能实现的我使用Setter方法同样能实现啊。其实不然KVO存在还是有它的价值的,那么接下来我们细数一下KVO的独特价值吧:
1.我们创建一两个setter方法感觉没什么,但是如果要观察的属性非常多,那么还能一一重写setter方法来实现吗?想必大家心里已有了答案,但是利用KVO则能很好的解决上述问题。
2.我们自定义的类是很容易改写setter方法的,但是如果你是用一个已经编译好了的类库时要监控其中一个属性时怎么办?难道还要去重写setter方法?如果使用KVO则很轻松解决问题。
3.使用KVO能够方便的记录变化前的值和变化后的值,不适用KVO你还要自己来解决这些问题。
4.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 )<span id = "1_nwp" style= "width: auto; height: auto; float: none;" ><a id = "1_nwl" href= "http://cpro.baidu.com/cpro/ui/uijs.php?app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=38d9bf7e00a82595&k=double&k0=double&kdi0=0&luki=4&n=10&p=baidu&q=69003180_cpr&rb=0&rs=1&seller_id=1&sid=9525a8007ebfd938&ssp2=1&stid=0&t=tpclicked3_hc&tu=u2116751&u=http%3A%2F%2Fwww%2Eandroiddev%2Enet%2Fkvo%2F&urlid=0" target= "_blank" mpid= "1" style= "text-decoration: none;" ><span style= "color:#0000ff;font-size:12px;width:auto;height:auto;float:none;" > double </span></a></span> 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 ( @"<span id=" 2_nwp " style=" width: auto; height: auto; float : none; "><a id=" 2_nwl " href=" http: //cpro.baidu.com/cpro/ui/uijs.php?app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=38d9bf7e00a82595&k=%D0%C2%BF%CE%B3%CC&k0=%D0%C2%BF%CE%B3%CC&kdi0=0&luki=5&n=10&p=baidu&q=69003180_cpr&rb=0&rs=1&seller_id=1&sid=9525a8007ebfd938&ssp2=1&stid=0&t=tpclicked3_hc&tu=u2116751&u=http%3A%2F%2Fwww%2Eandroiddev%2Enet%2Fkvo%2F&urlid=0" target="_blank" mpid="2" style="text-decoration: none;"><span style="color:#0000ff;font-size:12px;width:auto;height:auto;float:none;">新课程</span></a></span>是:%@ 老课程是:%@",[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 |
} |
当我们观察的那个值与多个其他的值有关时我们应该重写
+ (NSSet *)keyPathsForValuesAffecting<key>方法
其中<key>是你要监听的那个值的名字如rect
+ (NSSet *)keyPathsForValuesAffectingRect
通过滑动slider来改变相关的值,相关的只发生变化后从而改变标签的颜色。