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: forKeyPathremoveObserver: forKeyPath: context:
  • 回调监听:observeValueForKeyPath: ofObject: change: context:

KVO的使用步骤也比较简单:

  1. 通过addObserver: forKeyPath: options: context:为被监听对象(它通常是数据模型)注册监听器 
  2. 重写监听器的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
  1. .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文件