KVC与KVO纠缠不清的关系
KVC 与 KVO 是 Objective C 的关键概念,很多初学者特别容易被搞混了,个人认为必须理解的东西,下面是实例讲解。
Key-Value Coding (KVC)
一,概述
KVC是KeyValueCoding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。
一个对象拥有某些属性。比如说,一个 Person 对象有一个 name 和一个 address 属性。以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 address 的 key。 key 只是一个字符串,它对应的值可以是任意类型的对象。从最基础的层次上看,KVC 有两个方法:一个是设置 key 的值,另一个是获取 key 的值。如下面三的例子:(另外同KVO一样Core Data、CocoaBindings、AppleScript(Mac支持)时,KVC是关键技术。)
二,使用方法
关键方法定义在:
NSKeyValueCodingprotocol
KVC支持类对象和内建基本数据类型。
获取值
valueForKey:,传入NSString属性的名字。
valueForKeyPath:,传入NSString属性的路径,xx.xx形式。
valueForUndefinedKey它的默认实现是抛出异常,可以重写这函数做错误处理。
修改值
setValue:forKey:
setValue:forKeyPath:
setValue:forUndefinedKey:
setNilValueForKey:当对非类对象属性设置nil时,调用,默认抛出异常。
一对多关系成员的情况
mutableArrayValueForKey:有序一对多关系成员 NSArray
mutableSetValueForKey:无序一对多关系成员 NSSet
三,注意
1.对于valueForKey:方法用于以字符串调用对象的get属性方法,或者读取成员变量的值;与之相对的是setValue:forKey:,它用于以字符串调用对象的set属性方法,或者修改成员变量的值。
2.对于基本数据类型,KVC方法会对基本数据类型进行封装(基本数据类型封装为NSNumber,其他结构体类型封装为NSValue)。
3.默认情况下KVC方法能够直接访问类的私有成员变量,如果我们不想这样,可以重写方法,并令其返回NO(默认是返回YES)。KVC方法定义在NSKeyValueCoding类别中,该类别附加于NSObject类上,所以所有对象都具有这些方法。
4.在一些特殊的类的对象上调用KVC方法会有特别的效果。对于数组NSArray、集合NSSet,调用valueForKey:会对每个数组和集合成员调用valueForKey:,并返回新的数组或者集合。
5.使用KVC的好处是非常灵活,但同时也丢失了编译时检查。
四,实例:
void changeName(Person *p, NSString *newName)
{
NSString *originalName = [p valueForKey:@"name"];
[p setValue:newName forKey:@"name"];
NSLog(@"Changed %@'s name to: %@", originalName, newName);
}
现在,如果 Person 有另外一个 key 配偶(spouse),spouse 的 key 值是另一个 Person 对象,用 KVC 可以这样写:
void logMarriage(Person *p)
{
NSString *personsName = [p valueForKey:@"name"];
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。
Key-Value Observing (KVO)
一,概述
Key-Value Observing (KVO) 建立在 KVC 之上,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知,它能够观察一个对象的 KVC key path 值的变化。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
二,使用方法
系统框架已经支持KVO,所以程序员在使用的时候非常简单。
1 . 注册,指定被观察者的属性(实现观察)
2 . 实现回调方法(在被观察的 key path 的值变化时调用)
3 . 移除观察(dealloc)
三,实例:
做一个类似于血量条的例子
1.实现观察对象
// 设置一个Model
_model = [[Model alloc] init];
// 观察_model对象的volumn属性的变化
[_model addObserver:self forKeyPath:@"volumn" options:NSKeyValueObservingOptionNew context:(__bridge void *)(_pulseView)];
}
2.操作model
// 操作model
- (void)pullAction:(UISwipeGestureRecognizer *)gesture
{
if (gesture.direction == UISwipeGestureRecognizerDirectionUp) {
if (self.model.volumn < 400) {
self.model.volumn += 100;
}else{
UIAlertView *alertup = [[UIAlertView alloc] initWithTitle:@"警告" message:@"够了够了 ,你还想弄啥" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
[alertup show];
return ;
}
} else {
if (self.model.volumn > 100) {
self.model.volumn -= 100;
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"警告" message:@"血量严重不足!" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
[alert show];
return;
}
}
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"volumn"]) {
QYView *view = (__bridge QYView *)(context);
CGRect frame = view.frame;
frame.size.height = [change[NSKeyValueChangeNewKey] intValue];
frame.origin.y = _maxY - frame.size.height;
[UIView animateWithDuration:0.3 animations:^{
view.frame = frame;
view.backgroundColor = self.colors[(_model.volumn/100)-1];
}];
}
}
3.移除观察
增加观察与取消观察是成对出现的,所以需要在最后的时候,移除观察者
- (void)dealloc
{
[self removeObserver:self forKeyPath:@"volumn" context:(__bridge void *)(_pulseView)];
}
4.另附操作手势,观察血量变化显示颜色
- (NSArray *)colors
{
if (_colors == nil) {
_colors = @[[UIColor purpleColor], [UIColor redColor], [UIColor yellowColor], [UIColor greenColor]];
}
return _colors;
}
- (void)viewDidLoad {
[super viewDidLoad];
// View
_pulseView = [[NSBundle mainBundle] loadNibNamed:@"QYView" owner:self options:0][0];
[self.view addSubview:_pulseView];
_pulseView.center = self.view.center;
_maxY = CGRectGetMaxY(_pulseView.frame);
// 添加手势识别器
UISwipeGestureRecognizer *pullUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(pullAction:)];
pullUp.direction = UISwipeGestureRecognizerDirectionUp;
[_pulseView addGestureRecognizer:pullUp];
UISwipeGestureRecognizer *pullDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(pullAction:)];
pullDown.direction = UISwipeGestureRecognizerDirectionDown;
[_pulseView addGestureRecognizer:pullDown];
}
四,小结
KVO这种编码方式使用起来很简单,很适用与”voloumn”修改后,引发的UIVIew的变化这种情况,它通过 key path 观察对象的值就像上边的例子那样,当更改属性的值后,监听对象会立即得到通知。