Key-Value Observing (简写为KVO,键值监听):当指定的对象的属性被修改了,允许对象接受到通知的机制。每次指定的被观察对象的属性被修改的时候,KVO都会自动的去通知相应的观察者,相当于设计模式中的观察者模式。
KVO的优点:
当有属性改变,KVO会提供自动的消息通知。这样的架构有很多好处。首先,开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。这是KVO 机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。开发人员不需要添加任何代码,不需要设计自己的观察者模型,直接可 以在工程里使用。其次,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的使用步骤也比较简单:
- 通过addObserver: forKeyPath: options: context:为被监听对象(它通常是数据模型)注册监听器
- 重写监听器的observeValueForKeyPath: ofObject: change: context:方法
//首先定义一个类,声明两个属性:
1 #import <Foundation/Foundation.h>
2
3 @interface UserAccount : NSObject
4
5 @property (nonatomic, copy) NSString *userName;
6 @property (nonatomic, assign) NSString *money;
7
8 @end
1 #import "UserAccount.h"
2
3 @implementation UserAccount
4
5 @end
//控制器
1 #import "ViewController.h"
2 #import "UserAccount.h"
3
4 @interface ViewController ()
5
6 @property (nonatomic, weak)UILabel *testLabel;
7 @property (nonatomic, strong)UserAccount *userAccount;
8 @end
9
10 @implementation ViewController
11
12 - (void)viewDidLoad {
13 [super viewDidLoad];
14
15 UserAccount *userAccount = [[UserAccount alloc] init];
16 [userAccount setValue:@"liangwei" forKey:@"userName"];
17 [userAccount setValue:@"500000" forKey:@"money"];
18 self.userAccount = userAccount;
19
20 // 使用KVO为self.userAccount对象添加一个观察者,用于观察监听money属性值是否被修改 选项参数指定了发送变更通时提供给观察者的信息。使用NSKeyValueObservingOptionOld选项可以将初始对象值以变更字典中的一个项的形式提供给观察者。指定NSKeyValueObservingOptionNew选项可以将新的值以一个项的形式添加至变更字典。
21
22 // 第一个参数:监听者,可以直接传递self
23 // 第二个参数:监听对象的属性名
24 // 第三个参数:监听这个属性的状态:这里可以使用|进行多种组合操作,属性的新值和旧值
25 // 第四个参数:传递内容给监听方法
26
27 [self.userAccount addObserver:self forKeyPath:@"money" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
28
29
30 UILabel *testLable = [[UILabel alloc] init];
31 testLable.frame = CGRectMake(100, 70, 100, 30);
32 testLable.backgroundColor = [UIColor redColor];
33 testLable.text = [userAccount valueForKey:@"money"];
34 [self.view addSubview:testLable];
35 self.testLabel = testLable;
36
37 UIButton *testButton=[UIButton buttonWithType:UIButtonTypeRoundedRect];
38
39 [testButton setFrame:CGRectMake(20, 100, 100, 70)];
40
41 [testButton setTitle:@"测试" forState:UIControlStateNormal];
42 [testButton addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
43 [self.view addSubview:testButton];
44
45
46 }
47
48 - (void)btnClick
49 {
50
51 //注意执行到这一步会触发监听器回调函数observeValueForKeyPath: ofObject: change: context:
52 //[self.userAccount setValue:@"a" forKey:@"money"];
53 self.userAccount.money = @"100";
54
55 }
56
57 // 我们上面传递的第一个参数是监听者,这个方法也是在监听者中实现的,当属性值发生变化的时候,这个方法会被回调
58 //第一个参数:键值路径
59 //第二个参数:监听对象
60 //第三个参数:变化的值
61 //第四个参数:传递的内容
62 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
63 {
64 NSLog(@"1111");
65
66 if([keyPath isEqualToString:@"money"])
67 {
68 //这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
69 //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
70 //[change objectForKey:@"old"]是修改前的值
71 //NSNumber *hapyValue = [change objectForKey:@"new"];//修改之后的最新值
72
73 // self.testLabel.text = [self.userAccount valueForKey:@"money"];
74 // self.testLabel.text = self.userAccount.money;
75 self.testLabel.text = [change objectForKey:@"new"];
76
77
78
79 }
80 }
81
82 //在销毁方法中需要移除监听者
83 - (void)dealloc
84 {
85 [self.userAccount removeObserver:self forKeyPath:@"money"];
86
87 }
88
89
90
91
92
93 @end
KVC:Key-Value Coding,直译是:键值编码。简单来讲,就是给属性设置值的,就是可以暴力的去get/set类的私有属性,同时还有强大的键值路径对数组类型的属性进行操作
如何使用KVC存取对象属性呢?看个示例
定义一个Student类,继承于NSObject。
.h文件
1 #import <Foundation/Foundation.h>
2
3 @interface Student : NSObject
4 {
5 NSString *name;
6 }
7 @end
- .m文件
#import "Student.h"
@implementation Student
@end
.m文件也没有实现。name属性没有加property,原来的访问方法就访问不了name属性了。怎么办呢?用kvc就可以了
main.m文件
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *student = [[Student alloc] init];
[student setValue:@"张三" forKey:@"name"];
NSString *name = [student valueForKey:@"name"];
NSLog(@"学生姓名:%@",name);
}
return 0;
}
打印结果:
2015-09-21 00:17:41.117 KVC[2731:188556] 学生姓名:张三
Program ended with exit code: 0
张三 这个值存进去了,通过valueForKey取出来了。
如果存的时候key和类属性的名称不一致会怎么样呢?
代码改成
setValue:@"张三" forKey:@"name1"];
运行,程序崩溃 ,打印:
2015-09-21 00:19:41.372 KVC[2748:190104] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Student 0x1001143e0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name1.'
*** First throw call stack:
提示没有这个name1 这个key。
2、键路径访问属性
如果访问这个类里中的属性中的属性呢?那就用到了键路径
关键字:键路径取值valueForKeyPath 键路径存值:forKeyPath
新建一个类Course,课程类,课程类有课程名称这个属性
Course.h文件
1 #import <Foundation/Foundation.h>
2
3 @interface Course : NSObject
4 {
5
6 NSString *CourseName;
7
8 }
9
10 @end
Course.m文件
无内容
Student.h文件
1 #import <Foundation/Foundation.h>
2 #import "Course.h"
3
4 @interface Student : NSObject
5 {
6 NSString *name;
7 Course *course;
8
9 }
10
11 @end
Student.m文件
实现还是什么都没有,这里就不贴代码了
在main方法中,我们实验通过键路径访问Course中CourseName的属性
1 #import <Foundation/Foundation.h>
2 #import "Student.h"
3 #import "Course.h"
4
5 int main(int argc, const char * argv[]) {
6 @autoreleasepool {
7 Student *student = [[Student alloc] init];
8 [student setValue:@"张三" forKey:@"name"];
9 NSString *name = [student valueForKey:@"name"];
10 NSLog(@"学生姓名:%@",name);
11
12
13 Course *course = [[Course alloc]init];
14 [course setValue:@"语文课" forKey:@"CourseName"];
15 [student setValue:course forKey:@"course"];
16 NSString *courseName = [student valueForKeyPath:@"course.CourseName"];
17 NSLog(@"课程名称:%@", courseName);
18
19 //也可以这样存值
20 [student setValue:@"数学课" forKeyPath:@"course.CourseName"];
21 courseName = [student valueForKeyPath:@"course.CourseName"];
22 NSLog(@"课程名称:%@", courseName);
23 }
24 return 0;
25 }
2015-09-21 00:27:36.954 KVC[2813:195410] 学生姓名:张三
2015-09-21 00:27:36.955 KVC[2813:195410] 课程名称:语文课
2015-09-21 00:27:36.956 KVC[2813:195410] 课程名称:数学课
Program ended with exit code: 0
3、自动封装基本数据类型
我们在Student类中添加分数属性 NSInteger point;
.h文件