IOS之KVO和KVC

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和类属性的名称不一致会怎么样呢?

代码改成 

        [student 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文件

 1 #import <Foundation/Foundation.h>
 2 #import "Course.h"
 3 
 4 @interface Student : NSObject
 5 {
 6     NSString *name;
 7     Course *course;
 8     NSInteger point;
 9 
10 }
11 
12 @end

.m文件不改变就不管了

下面是main示例了

 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         
25         [student setValue:@"88" forKeyPath:@"point"];
26         NSString *point = [student valueForKey:@"point"];
27         NSLog(@"分数:%@", point);
28     }
29     return 0;
30 }

2015-09-21 09:13:32.972 KVC[2902:221111] 学生姓名:张三

2015-09-21 09:13:32.973 KVC[2902:221111] 课程名称:语文课

2015-09-21 09:13:32.973 KVC[2902:221111] 课程名称:数学课

2015-09-21 09:13:32.973 KVC[2902:221111] 分数:88

Program ended with exit code: 0

我们用NSString*类型设置的属性值@"88",而我们的属性是NSInteger类型的,存取都没有问题。

4、操作集合

在Student类中加入数组NSArray,用来表示其他的学生。这样我们可以添加多个其他的学生,再用集合操作计算学生的分数,最高分,最低分,平均分等

.m文件实现不变。

在main函数中添加三个学生,添加到数组中,然后求平均分,最高,最低分,学生数量

#import <Foundation/Foundation.h>
#import "Student.h"
#import "Course.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);
        
        [student setValue:@"88" forKey:@"point"];
        NSString *point = [student valueForKey:@"point"];
        NSLog(@"分数:%@", point);
        
        Student *student1 = [[Student alloc] init];
        Student *student2 = [[Student alloc] init];
        Student *student3 = [[Student alloc] init];
        [student1 setValue:@"90" forKey:@"point"];
        [student2 setValue:@"79" forKey:@"point"];
        [student3 setValue:@"89" forKey:@"point"];
        NSArray *array = [NSArray arrayWithObjects:student1,student2,student3,nil];
        [student setValue:array forKey:@"otherStudent"];
        NSLog(@"其他学生的成绩%@", [student valueForKeyPath:@"otherStudent.point"]);
        NSLog(@"共%@个学生", [student valueForKeyPath:@"otherStudent.@count"]);
        NSLog(@"最高成绩:%@", [student valueForKeyPath:@"otherStudent.@max.point"]);
        NSLog(@"最低成绩:%@", [student valueForKeyPath:@"otherStudent.@min.point"]);
        NSLog(@"平均成绩:%@", [student valueForKeyPath:@"otherStudent.@avg.point"]);      }
    return 0;
}

打印结果:

2015-09-21 09:21:11.153 KVC[2936:228011] 学生姓名:张三

2015-09-21 09:21:11.155 KVC[2936:228011] 分数:88

2015-09-21 09:21:11.156 KVC[2936:228011] 其他学生的成绩(

    90,

    79,

    89

)

2015-09-21 09:21:11.156 KVC[2936:228011] 3个学生

2015-09-21 09:21:11.156 KVC[2936:228011] 最高成绩:90

2015-09-21 09:21:11.156 KVC[2936:228011] 最低成绩:79

2015-09-21 09:21:11.156 KVC[2936:228011] 平均成绩:86

Program ended with exit code: 0

 
posted on 2015-09-21 09:25  jim93  阅读(95)  评论(0)    收藏  举报