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: 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就可以了
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.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文件
浙公网安备 33010602011771号