一、概述
KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解。
Key-Value Coding (KVC)
KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。
一个对象拥有某些属性。比如说,一个 Person 对象有一个 name 和一个 address 属性。以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 address 的 key。 key 只是一个字符串,它对应的值可以是任意类型的对象。从最基础的层次上看,KVC 有两个方法:一个是设置 key 的值,另一个是获取 key 的值。如下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
void changeName(Person *p, NSString *newName) { // using the KVC accessor (getter) method NSString *originalName = [p valueForKey:@ "name" ]; // using the KVC accessor (setter) method. [p setValue:newName forKey:@ "name" ]; NSLog(@ "Changed %@'s name to: %@" , originalName, newName); } |
现在,如果 Person 有另外一个 key 配偶(spouse),spouse 的 key 值是另一个 Person 对象,用 KVC 可以这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void logMarriage(Person *p) { // just using the accessor again, same as example above NSString *personsName = [p valueForKey:@ "name" ]; // this line is different, because it is using // a "key path" instead of a normal "key" NSString *spousesName = [p valueForKeyPath:@ "spouse.name" ]; NSLog(@ "%@ is happily married to %@" , personsName, spousesName); } |
key 与 key pat 要区分开来,key 可以从一个对象中获取值,而 key path 可以将多个 key 用点号 “.” 分割连接起来,比如:
[p valueForKeyPath:@ "spouse.name" ]; |
相当于这样……
[[p valueForKey:@ "spouse" ] valueForKey:@ "name" ]; |
好了,以上是 KVC 的基本知识,接着看看 KVO。
KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
KVO其实也是“观察者”设计模式的一种应用。我的看法是,这种模式有利于两个类间的解耦合,尤其是对于 业务逻辑与视图控制 这两个功能的解耦合。
引子
先来看个引子:
有一个业务类:Walker,在这个类内部只负责关于业务逻辑的处理,比如负责从服务器传来的JSON中解析数据,或做其他业务数据上的处理。
有另一个类:ViewController,专门负责界面的交互与试图更新。其中,需要讲Walker的某些属性显示出来,并实时更新。
目前,据我所能想到的方法有以下几种:
方法1、直接的函数调用
在Walker的类内部,把创建一个ViewController的对象,然后调用ViewController的修改界面的方法,把需要改动的属性值作为形参传给该函数。
这种方式最直观,因为它不需要绕任何弯子。但是,确实最糟的方法。因为Walker与ViewController这两个类从此紧紧耦合在一起了。记住这句话,处理业务逻辑的类,对外部的事情知道得越少越好。甚至于,要做到外部是否有VC(View Controller),有多少个VC都不影响我。假设这是一个项目,程序员A负责业务逻辑的处理,程序员B负责UI,则采取这种方式后,程序员A就受制于B,互相干扰。
方法2、利用消息通信机制(NSNotification)
在Walker内部建立消息中心NSNotificationCenter,把实例化之后的VC对象作为observer。Center建在Walker或者VC都无所谓,具体看我博客的另一篇文章【NSNotificationCenter总结】。这种方法比前面的方法好一些。但是有一个很大的缺点:如果Walker需要更改的属性很多而且很频繁,那么这种方式很不方便传值。而且,注意到了没,“把实例化后的VC对象作为observer”,始终逃不开在Walker内部对VC实例化。依旧是耦合着。
方法3、利用delegate
关于delegate的介绍有很多,这里就不多讲。但是在这种需求下用 delegate,有点“杀鸡用牛刀”感觉,成本较大,而且不直观。
方法4、利用KVO模式
所有的代码都将在ViewController中实现。对于Walker,它自己都不知道外部是否有VC,以及VC会怎用用我的属性。
Demo
1 // 2 // Walker.h 3 // KVOExample 4 // 5 // Created by 老翁 on 13-7-29. 6 // Copyright (c) 2013年 wzl. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 @interface Walker : NSObject 12 { 13 NSInteger _age; 14 NSString *_name; 15 } 16 17 @property (nonatomic) NSInteger age; 18 @property (nonatomic, retain) NSString *name; 19 20 - (id)initWithName:(NSString *)name age:(NSInteger)age; 21 22 23 @end
#import "Walker.h" @implementation Walker @synthesize age = _age; @synthesize name = _name; - (void)dealloc { [_name release]; [super dealloc]; } - (id)initWithName:(NSString *)name age:(NSInteger)age { if (self = [super init]) { _name = name; _age = age; } return self; } @end
ViewController代码:
// // ViewController.h // KVOExample // // Created by 老翁 on 13-7-29. // Copyright (c) 2013年 wzl. All rights reserved. // #import <UIKit/UIKit.h> #import "Walker.h" @class Walker; @interface ViewController : UIViewController { Walker *_walker; UILabel *_ageLabel; } @property (nonatomic, assign) Walker *walker; @end
1 // 2 // ViewController.m 3 // KVOExample 4 // 5 // Created by 老翁 on 13-7-29. 6 // Copyright (c) 2013年 wzl. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 11 @interface ViewController () 12 13 @end 14 15 @implementation ViewController 16 17 @synthesize walker = _walker; 18 19 20 - (void)dealloc 21 { 22 [_walker release]; 23 [_ageLabel release]; 24 [super dealloc]; 25 } 26 27 - (void)viewDidLoad 28 { 29 [super viewDidLoad]; 30 // Do any additional setup after loading the view, typically from a nib. 31 _walker = [[Walker alloc] initWithName:@"老翁" age:25]; 32 /* 关键步骤 */ 33 [_walker addObserver:self 34 forKeyPath:@"age" 35 options:NSKeyValueObservingOptionNew 36 context:nil]; 37 38 39 //UI initialization 40 UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; 41 [btn setFrame:CGRectMake(120, 200, 100, 35)]; 42 [btn setBackgroundColor:[UIColor lightGrayColor]]; 43 [btn setTitle:@"增加5岁" forState:UIControlStateNormal]; 44 [btn addTarget:self 45 action:@selector(buttonPressed) 46 forControlEvents:UIControlEventTouchUpInside]; 47 [self.view addSubview:btn]; 48 49 _ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 150, 200, 35)]; 50 _ageLabel.text = [NSString stringWithFormat:@"%@现在的年龄是: %d", _walker.name, _walker.age]; 51 _ageLabel.backgroundColor = [UIColor clearColor]; 52 [self.view addSubview:_ageLabel]; 53 54 55 } 56 57 - (void)buttonPressed 58 { 59 _walker.age += 5; 60 } 61 62 /* KVO function, 只要object的keyPath属性发生变化,就会调用此函数*/ 63 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 64 { 65 if ([keyPath isEqualToString:@"age"] && object == _walker) { 66 _ageLabel.text = [NSString stringWithFormat:@"%@现在的年龄是: %d", _walker.name, _walker.age]; 67 } 68 } 69 70 71 72 - (void)didReceiveMemoryWarning 73 { 74 [super didReceiveMemoryWarning]; 75 // Dispose of any resources that can be recreated. 76 } 77 78 @end