Objective-C基础教程学习笔记(十六)键/值编码

来源:互联网 发布:吉他扒谱软件 编辑:程序博客网 时间:2024/05/16 10:04

现在回过头来看一下间接机制。许多编程技术都基于间接机制,包括整个面向对象编程领域。本章将介绍另一种间接机制,这种机制不属于Objective-C语言的特性,而是Cocoa提供的一种特性。

到目前为止,我们已经介绍了通过直接调用方法、属性的点表示法或设置实例变量来直接更改对象状态。许多人将键/值编码亲切地称为KVC,它是一种间接更改对象状态的方式,其实现方法是使用字符串描述要更改的对象状态部分。

一些更高级的Cocoa特性,例如Core Data 和Cocoa Bindings,在基础机制中包含了KVC。

 

KVC简介

键/值编码中的基本调用包括-valueForKey:和-setValue:forKey:。以字符串的形式向对象发送消息,这个字符串是我们关注的属性的关键。

因此,我们可以请求car的名称:

NSString *name = [car valueForKey:@”name”];

这行代码可以获得name值。

valueForKey:首先查找以键-key或-isKey命名的getter方法。对于 这两种调用,valueForKey:查找-name。如果不存在getter方法,它将在对象内部 查找名为_key或key的实例变量。如果我们没有通过@synthesize提供存取方法,valueForKey将会查找实例变量_name和name。

最后一点非常重要:-valueForKey在Objective-C运行中使用元数据打开对象并进入其中查找需要的信息。在C或C++语言中不能执行这种操作。通过使用KVC,可以获取不存在getter方法的对象值,无需通过对象指针直接访问实例变量。

对于 KVC,Cocoa自动放入和取出标量值。也就是说,当使用setValueForKey时,它自动将标量值(int、float和struct)放入NSNumber或NSValue中;当使用-setValueForKey时它自动将标量值从这些对象中取出。仅KVC具有这种自动包装功能。常规方法调用和属性语法不具备该功能。

除用于检索值外,还可以使用-setValue:forKey:按名称设置值:

[car setValue:@”Harold” forKey:@”name”];

这个方法的工作方式和-valueForKey:相同。它首先查找name的setter方法,例如-setName,并使用参数@”Harold”调用它。如果不存在setter方法,它将在类中查找名为name或_name的实例变量,然后为它赋值。

 

编译器和苹果公司都以下划线开头的形式保存实例变量名,如果你尝试在其它地方使用下划线,可能会出现严重的错误。这条规则实际上不是强制的,但如果不遵循它,你可能会遇到某种风险。

 

如果在调用-setValue:forKey:之前设置一个标量值,你需要将它包装起来:

[car setValue:[NSNumber numberWithFloat:2500.4] forKey:@”mileage”];

 

路径

除了通过键设置值外,键/值编码还支持指定键路径,像文件系统路径一样,你可以遵循一系列关系 来指定该路径。

为了更深入了解这项功能,不妨加大引擎的马力。向Engine添加一个新的实例变量:

@interface Engine:NSObject<NSCopying>{

int horsepower;

}

@end // Engine

我们没有添加任何存取方法或特性。通过,我们希望为关注的对象属性使用存取方法或特性。但这里我们避免使用它们,以便真实地展示KVC直接深入到对象中的功能。

为了以非零马力启动引擎,我们添加一个init方法:

-(id) init{

if(self = [super init]){

horsepower = 145;

}

return self;

} //init

以下代码:

NSLog(@“horsepower is %@”,[engine valueForKey:@”horsepower”]);

[engine setValue:[NSNumber numberWithInt:150] forKey:@”horsepower”];

NSLog(@”horsepower is %@”,[engine valueForKey:@”horsepower”]);

输出:

horsepower is 145

horsepower is 150

如果表示这些键路径呢?可以指定以点分隔的不同属性名称。这样,通过查询car的”engine.horsepower”,就能够获取马力值。我们实际尝试一下使用-valueForKeypath和-setValueForKeyPath方法访问键路径。将以下消息发送给car,而不发送给engine:

[car setValue:[NSNumber numberWithInt:155] forKeyPath:@”engine.horsepower”];

NSLog(@”horsepower is %@”,[car valueForKeyPath:@”engine.horsepower”]);

这些键路径的深度是任意的,具体取决于对象图的复杂度,可以使用诸如“car.interior.airconditioner.fan.velocity”这样的键路径。在某种程序上,使用键路径比使用一系列嵌套方法调用更容易访问对象。

 

整体操作

关于KVC非常棒的一点是,如果向NSArray请求一个键值,它实际上会查询数组中的每个对象来查找这个键值,然后将查询结果打包到另一个数组中并返回给你。这种方法也适用于通过键路径访问的对象内容的数组。

在KVC中,通常认为嵌入到其它对象中的NSArray具有一对多的关系。例如,汽车与多个轮胎存在联系。因此,我们可以说Car与Tire之间存在一对多的关系 .如果键路径中含有一个数组属性,则该键路径的其余部分将被发送给数组的每个对象。

Car具有一个轮胎数组,并且每个轮胎都有自己的空气压力。通过一次调用,我们可以获取所有的轮胎压力值:

NSArray *pressures = [car valueForKeyPath:@”tires.pressure”];

执行以下调用 

NSLog(@”pressures %@”,pressures);

输出结果:

pressures (

34,

34,

34,

34

)

除了让我们知道轮胎状态之外,这里还发生了什么呢?valueForKeyPath:将路径分解并从左向右进行处理。首先,它向car请求轮胎信息。获取轮胎信息后,它通过键路径的其余部分请求tires对象的valueForKeyPath:,在本例中为“pressure”。NSArray实现valueForKeyPath:方法,循环查找其内容并将消息发送给每个对象。因此,使用“pressure”作为键路径,NSArray向内部的每个轮胎发送一个valueForKeyPath:,结果会将轮胎压力值打包到NSNumber中并返回。

然而,不能在键路径中为这些数组使用索引,例如,通过使用“tires[0].pressure”获取第一个轮胎的压力值。

 

流畅的计算

键路径不仅能引用对象值,还可以引用一些运算符来进行一些运算,例如获取一组值的平均值或返回这组值中的最小值和最大值。

例如,通过以下代码来计算汽车的数量:

NSNumber *count;

count = [garage valueForKeyPath:@”cars.@count”];

该程序会输出cars数组的长度。

我们将键路径”cars.@count”拆开,cars用来获取cars属性,它是来自garage的NSArray类型的值。我们知道,它是一个NSMutableArray,但如果我们不打算更改任何内容,可以将它视为NSArray。接下来是@count,其中的@符号意味着后面将进行一些运算。对编译器来说,@“blah”是一个字符串,@interface用于引入一个类。此处的@count用于通知KVC机制计算键路径左侧的结果。

我们还可以计算某些特定值的总和,例如,车队行驶的总英里数:

NSNumber *sum;

sum = [garage valueForKeyPath:@”cars.@sum.mileage”];

这项功能是如何实现的?@sum运算符将键路径分成两部分。第一部分可以看成多对关系的键路径,在本例中代表cars数组。另一部分可以看成包含一对多关系的任何键路径。它被当作用于关系中每个对象的键路径。mileage被发送到cars描述的关系中的每个对象,然后将这些结果值相加。

如果需要得到平均每辆汽车行驶的距离,可以将总数再除以汽车数量。但是还有一种更简单的方法:

NSNumber *avgMileage;

avgMileage = [garage valueForKeyPath:@”cars.@avg.mileage”];

如果没有键/值编码这个优点,我们必须对汽车编写一个循环,查找每辆汽车的行驶距离,将它们累加,然后再除以汽车数量——虽然不是很难,但还是需要一小段代码。

还有@min和@max运算符。

 

KVC不是免费的

KVC能非常轻松地处理集合。那么 ,为什么不使用KVC来处理所有对象,并且抛弃存取方法和代码编写?天下没有免费的午餐,除非你在某个硅谷技术大公司工作。KVC需要解析字符串来计算你需要的答案,因此速度比较慢。此处,编译器还无法对它进行错误检查。你可能想要处理karz.@avg.millage:,但编译器不能判断它是否是错误的键路径。因此,当你尝试使用它时,就会出现运行时错误。

此外,你无法添加自己的运算符。


原创粉丝点击