- 許多編程思想基于間接機(jī)制顾瞻,我們將介紹另一間接機(jī)制,這種機(jī)制不屬于Objective-C語(yǔ)言的特性蕴纳,而是Cocoa提供的一種特性。
- 到目前為止稻薇,我們已經(jīng)介紹了通過直接調(diào)用方法、屬性的點(diǎn)表示或設(shè)置實(shí)例變量來直接更改對(duì)象狀態(tài)忱屑。鍵/值編碼(key-value coding)是一種間接更改對(duì)象狀態(tài)的方式伴嗡,許多人稱之為KVC,其實(shí)現(xiàn)方法是使用字符串表示要更改的對(duì)象的狀態(tài)阱扬。
1.入門項(xiàng)目
- 繼續(xù)使用Car項(xiàng)目信夫,為了讓項(xiàng)目更加生動(dòng)墨技,我們想Car類添加了一些屬性杀迹,例如常見的品牌和型號(hào)。
@interface Car : NSObject <NSCopying> {
NSString *name;
NSMutableArray *tires;
Engine *engine;
NSString *make;
NSString *model;
int modelYear;
int numberOfDoors;
float mileage;
}
@property (readwrite, copy) NSString *name;
@property (readwrite, retain) Engine *engine;
@property (readwrite, copy) NSString *make;
@property (readwrite, copy) NSString *model;
@property (readwrite) int modelYear;
@property (readwrite) int numberOfDoors;
@property (readwrite) float mileage;
...
@end // Car
- 我們添加了@synthesize指令,這樣編譯器將會(huì)自動(dòng)生成setter和getter方法绵载。
- 我們還修改了-copyWithZone方法苛白,以匹配新的屬性娃豹。
- 我們還更改了-description方法,以輸出這些新的屬性并省略Engine和Tire的詳細(xì)輸出购裙。
@implementation Car
@synthesize name;
@synthesize engine;
@synthesize make;
@synthesize model;
@synthesize modelYear;
@synthesize numberOfDoors;
@synthesize mileage;
- (id) copyWithZone: (NSZone *) zone
{
Car *carCopy;
carCopy = [[[self class]
allocWithZone: zone]
init];
carCopy.name = name;
carCopy.make = make;
carCopy.model = model;
carCopy.numberOfDoors = numberOfDoors;
carCopy.mileage = mileage;
Engine *engineCopy;
engineCopy = [[engine copy] autorelease];
carCopy.engine = engineCopy;
int i;
for (i = 0; i < 4; i++) {
Tire *tireCopy;
tireCopy = [[self tireAtIndex: i] copy];
[tireCopy autorelease];
[carCopy setTire: tireCopy
atIndex: i];
}
return (carCopy);
} // copyWithZone
- (NSString *) description {
NSString *desc;
desc = [NSMutableString stringWithFormat:
@"%@, a %@ %@, has %d doors, %.1f miles, and 4 tires:",
name, make, model, numberOfDoors, mileage];
return desc;
} // description
@end // Car
- 最后在main函數(shù)中懂版,我們將為car設(shè)定以上屬性,并將它們輸出躏率。同時(shí)躯畴,我們使用了autorelease以及alloc和init的方法饮亏,這樣可以在同一位置執(zhí)行完所有的內(nèi)存管理劝赔。
int main (int argc, const char * argv[])
{
@autoreleasepool
{
Car *car = [[[Car alloc] init] autorelease];
car.name = @"Herbie";
car.make = @"Honda";
car.model = @"CRX";
car.modelYear = 1984;
car.numberOfDoors = 2;
car.mileage = 110000;
for (int i = 0; i < 4; i++)
{
AllWeatherRadial *tire;
tire = [[AllWeatherRadial alloc] init];
[car setTire: tire atIndex: i];
[tire release];
}
Slant6 *engine = [[[Slant6 alloc] init] autorelease];
car.engine = engine;
NSLog (@"Car is %@", car);
}
}
運(yùn)行結(jié)果:Car is Herbie, a 1984 Honda CRX, has 2 doors, 110000.0 miles, and 4 tires.
2.KVC簡(jiǎn)介
鍵/值編碼中的基本調(diào)用是-valueForKey:和-setValue:方法观话。你可以向?qū)ο蟀l(fā)送消息,并傳遞你想要訪問的屬性名稱的鍵作為參數(shù)避咆。
那么整慎,我們可以這樣訪問car對(duì)象的name屬性:
NSString *name = [car valueForKey:@"name"];
NSLog(@"%@",name);
- 以上代碼將輸出Herbie丈氓。使用類似的方法,我們還可以獲取品牌信息。
NSLog(@"make is %@",[car valueForKey:@"make"]);
valueForKey:的功能非常強(qiáng)大贪壳,它可以找到make屬性的值并將其返回。
valueForKey:會(huì)首先查找以參數(shù)命名(格式為-key或-isKey)的getter方法识埋。對(duì)于以上兩個(gè)調(diào)用风宁,valueForKey:會(huì)先尋找-name和-make方法列吼。如果沒有這樣的getter方法,它將會(huì)在對(duì)象內(nèi)尋找名稱格式為_key或key的實(shí)例變量邻吭。如果我們沒有使用@synthesize來提供訪問方法,valueForKey方法將會(huì)尋找名稱為_name和name以及_make和make的實(shí)例變量淫痰。
最后,非常重要的一點(diǎn)是犹菇,-valueForKey在Objective-C運(yùn)行時(shí)中使用元數(shù)據(jù)打開對(duì)象并進(jìn)入其中查找需要的信息锭汛。在C或C++語(yǔ)言中不能執(zhí)行這種操作晋辆。通過使用KVC,沒有相關(guān)getter方法也能獲取對(duì)象值,不需要通過對(duì)象指針來直接訪問實(shí)例變量。
可以對(duì)型號(hào)年份使用同樣的技術(shù):
NSLog(@"model year is %@",[car valueFoeKey:@"modelYear"]);
它的輸出結(jié)果為:model year is 1984凌净。
NSLog中的%@是用來輸出對(duì)象的迁筛,但modelYear是一個(gè)int值导披,而不是對(duì)象膝昆。這該如何處理呢澄峰?對(duì)于KVC,Cocoa會(huì)自動(dòng)裝箱和開箱標(biāo)量值。也就是說爷绘,當(dāng)使用setValueForKey時(shí)楷扬,它自動(dòng)將標(biāo)量值(int捆探,float和struct)放入NSNumber和NSValue中揩环;當(dāng)使用-setValueForKey時(shí),它自動(dòng)將標(biāo)量值從這些對(duì)象中取出。僅KVC具有這種自動(dòng)裝箱功能儒旬,常規(guī)方法調(diào)用和屬性語(yǔ)法不具備該功能疗韵。
除了檢索值外兑障,還可以使用-setValue:forKey:方法依據(jù)名稱設(shè)置值。
[car setValue:@"Harold" forKey:@"name"];
這個(gè)方法的工作方式和-valueForKey:相同蕉汪。他首先查找名稱的setter方法流译,例如-setName,然后調(diào)用它并傳遞參數(shù)@"Harold"者疤。如果不存在setter方法先蒋,它將在類中尋找名為name或_name的實(shí)例變量,然后為它賦值宛渐。
謹(jǐn)記以下規(guī)則:編譯器和蘋果公司都以下劃線開頭的形式保存實(shí)例變量名稱竞漾,如果你嘗試在其他地方使用下劃線,可能會(huì)出現(xiàn)嚴(yán)重的錯(cuò)誤窥翩。你可以不遵守這條規(guī)則业岁,但如果不遵守它,有可能會(huì)遇到某種風(fēng)險(xiǎn)寇蚊。
如果你想要設(shè)置一個(gè)標(biāo)量值笔时,在調(diào)用-setValue:forKey:方法之前需要將它們包裝起來,也就是裝箱到對(duì)象中仗岸。
[car setValue:[NSNumber numberWithFloat: 25062.4] forKey:@"mileage"];
- 此外允耿,-setValue:forKey:方法會(huì)先開箱取出該值,再調(diào)用-setMileage:方法或更改mileage實(shí)例變量扒怖。
3.鍵路徑
除了通過鍵設(shè)置值以外较锡,鍵/值編碼還支持指定鍵路徑,就像文件系統(tǒng)路徑一樣盗痒,你可以遵循一系列關(guān)系來指定該路徑蚂蕴。
為了更深入的了解這項(xiàng)功能,不放加大引擎的馬力俯邓。向Engine類添加一個(gè)新的實(shí)例變量骡楼。
@interface Engine : NSObject<NSCopying>{
int horsepower;
}
@end
請(qǐng)注意我們沒有添加任何訪問方法或?qū)傩浴MǔP枰獮橛杏玫膶?duì)象添加訪問方法或?qū)傩曰蓿贿^為了展示KVC是如何直接獲取對(duì)象的鸟整,在這里我們不會(huì)使用這些方法。
為了讓引擎有馬力能夠啟動(dòng)朦蕴,我們添加了一個(gè)init方法篮条。
- (id) init {
if (self = [super init]) {
horsepower;
}
return self;
}
另外祠乃,我們也在-copyWithZone方法中添加了關(guān)于horsepower實(shí)例變量的代碼,這樣在復(fù)制對(duì)象時(shí)也能獲取這個(gè)值兑燥,并且在-description方法中也添加了相關(guān)代碼。我們已經(jīng)很熟悉這個(gè)方法了琴拧,所以這里就不詳細(xì)介紹了降瞳。
在實(shí)現(xiàn)文件添加以下代碼,以確保能夠獲取并設(shè)置值蚓胸。
NSLog(@"horsepower is %@",[engine valueForKey:@"horsepower"]);
[engine setValue:[NSNumber numberWithInt:150] forKey:@"horsepower"];
NSLog(@"horsepower is %@",[engine valueForKey:@"horsepower"]);
運(yùn)行這段代碼將會(huì)輸出:horsepower is 145 horsepower is 150
如何表示這些鍵路徑呢挣饥?可以在對(duì)象和不同的變量名稱之間用圓點(diǎn)分開。通過查詢car的engine.horsepower沛膳,就能夠獲馬力值∪臃悖現(xiàn)在,我們?cè)囍褂?valueForKeyPath和-setValueForKeyPath方法來訪問鍵路徑锹安。將以下消息發(fā)送給car對(duì)象短荐,而不是發(fā)送給engine。
[car setValue:[NSNumber numberWithInt:150] forKey:@"horsepower"];
NSLog(@"horsepower is %@",[car valueForKey:@"horsepower"]);
- 這些鍵路徑的深度是任意的叹哭,具體取決于對(duì)象圖(object graph忍宋,可以表示對(duì)象之間的關(guān)系)的復(fù)雜度,可以使用諸如car.interior.airconditioner.fan.velocity這樣的鍵路徑风罩。在某種程度上糠排,使用鍵路徑比使用一些列嵌套方法調(diào)用更容易訪問到對(duì)象。
4.整體操作
關(guān)于KVC非常棒的一點(diǎn)是超升,如果使用某個(gè)關(guān)鍵值來訪問一個(gè)NSArray數(shù)組入宦,它實(shí)際上會(huì)查詢相應(yīng)數(shù)組中的每個(gè)對(duì)象,然后將查詢結(jié)果打包到另一個(gè)數(shù)組中并返回給你室琢。這種方法也同樣適用于通過鍵路徑訪問的位于對(duì)象中的數(shù)組(是不是想到了以前說過的復(fù)合乾闰?)。
在KVC中盈滴,通常認(rèn)為對(duì)象中的NSArray具有一對(duì)多的關(guān)系汹忠。舉個(gè)例子,汽車與多個(gè)(一般都是四個(gè))輪胎具有聯(lián)系雹熬。因此宽菜,我們可以說Car與Tire之間存在一對(duì)多的關(guān)系。如果鍵路徑中含有一個(gè)數(shù)組屬性竿报,則該鍵路徑的其余部分將被發(fā)送給數(shù)組中的每個(gè)對(duì)象铅乡。
一對(duì)一關(guān)系:你現(xiàn)在已經(jīng)了解了一對(duì)一關(guān)系,可能還想知道什么是一對(duì)一的關(guān)系烈菌。一般對(duì)象的復(fù)合都是一對(duì)一的關(guān)系阵幸。例如花履,汽車與引擎之間就是一對(duì)一的關(guān)系。
還記得Car類中有一個(gè)tires數(shù)組嗎挚赊?每個(gè)輪胎都有它自己的空氣壓力诡壁。我們可以在一個(gè)調(diào)用中獲取所有的輪胎壓力值。
NSArray *pressure = [car valueForKeyPath:@"tires.pressure"];
NSLog(@"pressures %@",pressures);
- 調(diào)用以下代碼之后荠割,就會(huì)輸出如下結(jié)果:
pressures(
34妹卿,
34,
34蔑鹦,
34)
除了告訴我們輪胎的狀態(tài)之外夺克,這里還發(fā)生了什么呢?valueForKeyPath:將路徑分解并從左向右進(jìn)行處理嚎朽。首先铺纽,它向car對(duì)象請(qǐng)求輪胎信息,然后使用鍵路徑的剩余部分(在本示例中是pressure)向tires對(duì)象調(diào)用valueForKeyPath:方法哟忍。NSArray實(shí)現(xiàn)valueForKeyPath:的方法是循環(huán)遍歷它的內(nèi)容并向每個(gè)對(duì)象發(fā)送消息狡门。因此,NSArray向每個(gè)在自身之中的tire對(duì)象發(fā)送了參數(shù)以pressure作為鍵路徑的valueForKeyPath:消息锅很,結(jié)果就會(huì)將tire對(duì)象的pressure變量封裝到NSNumber對(duì)象中并返回融撞。非常方便。
不幸的是粗蔚,不能在鍵路徑中索引這些數(shù)組尝偎,例如使用tires[0].pressure來獲取第一個(gè)輪胎的壓力值。
4.整體操作
4.1休息一下
- 在介紹鍵/值編碼的下一個(gè)優(yōu)點(diǎn)之前鹏控,我們將添加一個(gè)名為Garage的新類致扯,用于存放各種不同類型的car對(duì)象。下面是Garage類的接口類容当辐。
#import <Cocoa/Cocoa.h>
@class Car;
@interface Garage : NSObject {
NSString *name;
NSMutableArray *cars;
NSMutableDictionary *stuff;
}
@property (readwrite, copy) NSString *name;
- (void) addCar: (Car *) car;
- (void) print;
@end // Garage
此處沒有涉及新內(nèi)容抖僵。我們?cè)谝婚_始就聲明了Car類,因?yàn)樾枰肋@個(gè)對(duì)象類型被用作-addCar:方法的參數(shù)缘揪。name是一個(gè)屬性值耍群,而@property語(yǔ)句表示使用Garage類的人可以訪問和更改name屬性值。并且代碼中還有用來輸出對(duì)象內(nèi)容的方法找筝。為了實(shí)現(xiàn)一個(gè)cars對(duì)象集合蹈垢,我們添加一個(gè)cars對(duì)象集合,我們添加一個(gè)可變數(shù)組的實(shí)例變量袖裕。
實(shí)現(xiàn)代碼的內(nèi)容同樣很簡(jiǎn)單曹抬。
#import "Garage.h"
@implementation Garage
@synthesize name;
- (void) addCar: (Car *) car {
if (cars == nil) {
cars = [[NSMutableArray alloc] init];
}
[cars addObject: car];
} // addCar
- (void) dealloc {
[name release];
[cars release];
[stuff release];
[super dealloc];
} // dealloc
- (void) print {
NSLog (@"%@:", name);
for (Car *car in cars) {
NSLog (@" %@", car);
}
} // print
- (void) setValue: (id) value forUndefinedKey: (NSString *) key {
if (stuff == nil) {
stuff = [[NSMutableDictionary alloc] init];
}
[stuff setValue: value forKey: key];
} // setValueForUndefinedKey
- (id) valueForUndefinedKey:(NSString *)key {
id value = [stuff valueForKey: key];
return (value);
} // valueForUndefinedKey
@end // Car
像往常一樣,我們包含了Garage.h的頭文件并使用@synthesize合成了name屬性的存取方法急鳄。
-addCar:是cars數(shù)組懶性初始化的一個(gè)示例谤民,我們僅在需要時(shí)才創(chuàng)建它堰酿。-dealloc用于清理name屬性和數(shù)組,而-print遍歷數(shù)組并輸出各種類型汽車的信息张足。
main函數(shù)里面触创,我們先使用一個(gè)函數(shù)構(gòu)造汽車的各種屬性。我們可以創(chuàng)建一個(gè)Car類的類方法为牍,也可以創(chuàng)建多種類型的工廠類哼绑,因?yàn)镺bjective-C仍然是一種C語(yǔ)言,所以可以使用函數(shù)吵聪。再次,我們使用函數(shù)是因?yàn)榻M裝汽車的函數(shù)代碼與實(shí)際組裝汽車的方式比較接近兼雄。
Car *makeCar (NSString *name, NSString *make, NSString *model,
int modelYear, int numberOfDoors, float mileage,
int horsepower) {
Car *car = [[[Car alloc] init] autorelease];
car.name = name;
car.make = make;
car.model = model;
car.modelYear = modelYear;
car.numberOfDoors = numberOfDoors;
car.mileage = mileage;
Slant6 *engine = [[[Slant6 alloc] init] autorelease];
[engine setValue: [NSNumber numberWithInt: horsepower]
forKey: @"horsepower"];
car.engine = engine;
// Make some tires.
// int i;
for (int i = 0; i < 4; i++) {
Tire * tire= [[[Tire alloc] init] autorelease];
[car setTire: tire atIndex: i];
}
return (car);
} // makeCar
現(xiàn)在吟逝,上面的代碼你基本都已經(jīng)熟悉了。按照Cocoa的慣例構(gòu)造并自動(dòng)化釋放一個(gè)新Car對(duì)象赦肋,因?yàn)橥ㄟ^這個(gè)函數(shù)獲得的car對(duì)象沒有調(diào)用new块攒,copy或alloc方法。然后佃乘,我們?cè)O(shè)置了一些屬性囱井。請(qǐng)記住,這項(xiàng)技術(shù)與KVC不同趣避,我們沒有使用setValue:forKey方法庞呕。接下來,我們創(chuàng)建了一個(gè)engine對(duì)象程帕,因?yàn)槲覀儧]有為它創(chuàng)建存取方法住练,所以使用KVC設(shè)置馬力值。最后愁拭,構(gòu)造一些tire對(duì)象并將它們安置在car對(duì)象中讲逛。最后會(huì)返回新的car對(duì)象。
以下是新版的main()函數(shù)岭埠。
int main (int argc, const char * argv[])
{
@autoreleasepool
{
Garage *garage = [[Garage alloc] init];
garage.name = @"Joe's Garage";
Car *car;
car = makeCar (@"Herbie", @"Honda", @"CRX", 1984, 2, 110000, 58);
[garage addCar: car];
car = makeCar (@"Badger", @"Acura", @"Integra", 1987, 5, 217036.7, 130);
[garage addCar: car];
car = makeCar (@"Elvis", @"Acura", @"Legend", 1989, 4, 28123.4, 151);
[garage addCar: car];
car = makeCar (@"Phoenix", @"Pontiac", @"Firebird", 1969, 2, 85128.3, 345);
[garage addCar: car];
car = makeCar (@"Streaker", @"Pontiac", @"Silver Streak", 1950, 2, 39100.0, 36);
[garage addCar: car];
car = makeCar (@"Judge", @"Pontiac", @"GTO", 1969, 2, 45132.2, 370);
[garage addCar: car];
car = makeCar (@"Paper Car", @"Plymouth", @"Valiant", 1965, 2, 76800, 105);
[garage addCar: car];
[garage print];
[garage release];
}
return (0);
} // main
//運(yùn)行結(jié)果
2018-12-23 03:04:55.811624-0800 Car-Value-Garaging[570:12976] Joe's Garage:
2018-12-23 03:04:55.811864-0800 Car-Value-Garaging[570:12976] Herbie, a 1984 Honda CRX, has 2 doors, 110000.0 miles, 58 hp and 4 tires
2018-12-23 03:04:55.811891-0800 Car-Value-Garaging[570:12976] Badger, a 1987 Acura Integra, has 5 doors, 217036.7 miles, 130 hp and 4 tires
2018-12-23 03:04:55.811906-0800 Car-Value-Garaging[570:12976] Elvis, a 1989 Acura Legend, has 4 doors, 28123.4 miles, 151 hp and 4 tires
2018-12-23 03:04:55.811919-0800 Car-Value-Garaging[570:12976] Phoenix, a 1969 Pontiac Firebird, has 2 doors, 85128.3 miles, 345 hp and 4 tires
2018-12-23 03:04:55.811931-0800 Car-Value-Garaging[570:12976] Streaker, a 1950 Pontiac Silver Streak, has 2 doors, 39100.0 miles, 36 hp and 4 tires
2018-12-23 03:04:55.811943-0800 Car-Value-Garaging[570:12976] Judge, a 1969 Pontiac GTO, has 2 doors, 45132.2 miles, 370 hp and 4 tires
2018-12-23 03:04:55.811956-0800 Car-Value-Garaging[570:12976] Paper Car, a 1965 Plymouth Valiant, has 2 doors, 76800.0 miles, 105 hp and 4 tires
- main()函數(shù)進(jìn)行了一些信息輸入盏混,創(chuàng)建了一個(gè)garage對(duì)象,還創(chuàng)建了一些car對(duì)象并保存在garage對(duì)象中惜论。最后许赃,main函數(shù)輸出了garage對(duì)象的信息并將其釋放。
4.整體操作
4.2快速運(yùn)算
鍵路徑不僅能引用對(duì)象值馆类,還可以引用一些運(yùn)算符來進(jìn)行一些運(yùn)算图焰,例如能獲取一組值得平均值或返回這組值中的最小值和最大值。
舉個(gè)例子蹦掐,通過以下代碼可以計(jì)算汽車的數(shù)量技羔。
NSNumber *count;
count = [garage valueForKeyPath:@"count"];
NSLog(@"We have %@ cars",count);
//運(yùn)行結(jié)果
We have 7 cars
我們將鍵路徑cars.@count拆開來理解僵闯。cars用于獲取cars屬性,它是取自garage對(duì)象的NSArray類型的值藤滥。我們知道鳖粟,它是一個(gè)NSMutableArray,但如果我們不打算更改數(shù)組的任何內(nèi)容拙绊,可以將其視為NSArray類型向图。接下來是@count,其中的@符號(hào)意味著后面將進(jìn)行一些運(yùn)算标沪。對(duì)編譯器來說榄攀,@"blah"是一個(gè)字符串,而@interface用于聲明類金句。此處的@count用于通知KVC機(jī)制計(jì)算鍵路徑左側(cè)值的對(duì)象總數(shù)檩赢。
此外,我們還可以計(jì)算某些值的總和违寞,例如贞瞒,汽車行駛的總英里數(shù)。以下代碼段
NSNumber *sum;
sum = [garage valueForKeyPath:@"cars.@sum.mileage"];
NSLoag(@"We have a grand total of %@ miles",sum);
- 運(yùn)行后將輸出
We have a grand total of 601320.6 miles
這項(xiàng)功能是如何做到的呢趁曼?@sum運(yùn)算符將鍵路徑分成兩部分军浆。第一部分可以看成一對(duì)多關(guān)系的鍵路徑,在本例中代表cars數(shù)組挡闰。另一部分可以看成包含一對(duì)多關(guān)系的鍵路徑乒融。它被當(dāng)做用于關(guān)系中每個(gè)對(duì)象的鍵路徑。因此mileage消息被發(fā)送給了cars關(guān)系中所有的對(duì)象摄悯,然后將結(jié)果值相加簇抵。當(dāng)然,每個(gè)鍵路徑的長(zhǎng)度可以是任意的射众。
如果需要得到平均每輛汽車行駛的距離碟摆,可以用總數(shù)除以汽車數(shù)量,但還有一種更簡(jiǎn)單的方法叨橱。以下幾行代碼
NSNumber *avgMileage;
avgMileage = [garage valueForKeyPath:@"cars.@avg.mileage"];
NSLog(@"average is %.2f",[avgMileage floatValue]);
//運(yùn)行結(jié)果
average is 85902.95
如果沒有鍵/值編碼的這個(gè)優(yōu)點(diǎn)典蜕,我們還需要寫一段不短的代碼來完成。
現(xiàn)在我們將鍵路徑cars.@avg.mileage分開罗洗。和@sum一樣愉舔,@avg運(yùn)算符將鍵路徑分成了兩部分。在本例中伙菜,@avg之前的部分為cars轩缤,是汽車一對(duì)多關(guān)系的鍵路徑;@avg之后是另一個(gè)鍵路徑,它僅表示距離火的。在后臺(tái)壶愤,KVC能夠輕松地進(jìn)行循環(huán),將值累加馏鹤,并計(jì)算總數(shù)征椒,然后再進(jìn)行除法運(yùn)算。
還有@min和@max運(yùn)算符湃累,它們的功能很明顯勃救。
NSNumber *min,*max;
min = [garage valueForKeyPath:@"cars.@min.mileage"];
max = [garage valueForKeyPath:@"cars.@max.mileage"];
NSLog(@"minmax: %@/%@",min,max);
- 輸出結(jié)果為:
minmax: 28123.4/217036.7
不要濫用KVC:既然KVC能夠非常輕松地處理集合類,為什么不用它來處理所有對(duì)象治力,拋棄存取方法和其他代碼的編寫呢蒙秒?KVC需要解析字符串來計(jì)算你需要的答案,因此速度比較慢宵统。此外晕讲,編譯器還無(wú)法對(duì)它進(jìn)行錯(cuò)誤檢查。你可以想要處理karz.@avg.millage榜田,但編譯器不能判斷它是否是錯(cuò)誤的鍵路徑益兄。因此锻梳,當(dāng)你嘗試使用它時(shí)箭券,就會(huì)出現(xiàn)運(yùn)行錯(cuò)誤。
有時(shí)你使用的變量的值只有幾個(gè)疑枯,例如上面構(gòu)造的所有汽車辩块。即便我們有100萬(wàn)輛汽車,品牌的種類也會(huì)很少荆永。通過使用鍵路徑cars@distinctUnionOfObjects.make废亭,就可以從集合中只獲取各個(gè)品牌的名稱。
NSArray *manufacturers;manufacturers = [garage valueForKeyPath:@"cars.@distinctUnionOfObjects.make"];
NSLog(@"makers: %@",manufacturers);
- 運(yùn)行以上代碼具钥,將得到以下結(jié)果:
maker:(
Honda,
Plymouth,
Pontiac,
Acura
)
- 鍵路徑中間的運(yùn)算符名稱為@distinctUnionOfObjects豆村,它看上去很復(fù)雜,但由名稱就能了解它的功能骂删。它和其他運(yùn)算符的應(yīng)用原理相同:獲取左側(cè)指定的集合掌动,對(duì)該集合中的每個(gè)對(duì)象使用右側(cè)的鍵路徑,然后將結(jié)果轉(zhuǎn)換為一個(gè)集合宁玫。名稱中的union指一組對(duì)象的并集粗恢,distince用于刪除重復(fù)內(nèi)容。還有很多其他運(yùn)算符也沿用了這種工作方式欧瘪,這些內(nèi)容留給你們自己去探索眷射。不過,你無(wú)法添加自己的運(yùn)算符,這一點(diǎn)比較遺憾妖碉。
5.批處理
KVC包含兩個(gè)調(diào)用涌庭,可以使用它們?yōu)閷?duì)象進(jìn)行批量更改。第一個(gè)調(diào)用是dictionaryWithValuesForKeys:方法嗅绸,它接受一個(gè)字符串?dāng)?shù)組脾猛。該調(diào)用獲取鍵的名稱,并對(duì)每個(gè)鍵使用valueForKey:方法鱼鸠,然后為鍵字符串和剛剛獲取的值構(gòu)建一個(gè)字典猛拴。
我們從garage對(duì)象中挑選一個(gè)car對(duì)象,并使用其中一些變量來創(chuàng)建一個(gè)字典蚀狰。
car = [[garage valueForKeyPath:@"cars"] lastObject];
NSArray *keys = [NSArray arrayWithObjects:@"make",@"model",@"modelYear",nil];
NSDictionary *carValues = [car dictionaryWithValuesForKeys:keys];
NSLog(@"Car values : %@",carValues);
- 運(yùn)行以上代碼愉昆,我們將獲取一些相關(guān)信息。
Car values :{
make = Plymouth;
model = Valiant;
modelYear = 1965;
}
- 我們還可以更改這些值麻蹋,將Valiant變成新的型號(hào)跛溉,升級(jí)為Chevy Nova。
NSDictionary *newValues = [NSDictionary dictionaryWithObjectsAndKeys:@"Chevy",@"make",@"Nova",@"model",[NSNumber numberWithInt:1964],@"modelYear",nil];
[car setValuesForKeysWithDictionary:newValues];
NSLog(@"car with new values is %@",car);
- 運(yùn)行以上這些代碼后扮授,我們會(huì)發(fā)現(xiàn)它確實(shí)變成了一輛新車芳室。
car with new values is Paper Car,a 1964 Chevy Nova,has 2 doors,76800.0 miles, and 4 tires.
請(qǐng)注意,某些值(品牌刹勃、型號(hào)和年份)發(fā)生了變化堪侯,但是名稱和行駛距離等沒有變
在本程序中,這個(gè)工具不是特別有用荔仁,不過它還支持在用戶界面代碼中實(shí)現(xiàn)一些不錯(cuò)的功能伍宦。例如,通過蘋果公司的Aperture程序中的Lift and stamp工具乏梁,可以把對(duì)某一張圖片的部分修改同樣用在其他圖片上次洼。可以使用dictionaryWithValuesForKeys方法獲取所有變量遇骑,并將字典中的內(nèi)容全部顯示在用戶界面上卖毁。用戶可以使用setValuesForKeysWithDictionary方法獲取字典內(nèi)容并對(duì)其他圖片進(jìn)行更改。如果你正確地設(shè)計(jì)了你的用戶界面類落萎,也可以對(duì)其他對(duì)象(比如圖片亥啦,cars對(duì)象或食譜)使用相同的lift and stamp面板。
6.nil仍然可以用
字典不能包含nil值模暗,但如果出現(xiàn)nil值會(huì)怎樣呢(例如一輛沒有名字的汽車)禁悠?回想一下第七章的內(nèi)容,我們使用[NSNull null]表示nil值兑宇。同樣地碍侦,當(dāng)調(diào)用dictionaryWithValuesForKeys時(shí),對(duì)于沒有名稱的汽車,@"name"鍵下將返回[NSNull null]瓷产。你也可以為setValuesForKeysWithDictionary提供的[NSNull null]站玄,這樣汽車就會(huì)沒有名字了。
對(duì)nil值的討論引出了一個(gè)有趣的問題濒旦。標(biāo)量值(例如mileage)中的nil表示什么株旷?0?-1尔邓?圓周率晾剖?Cocoa無(wú)法知道它代表什么。你可以嘗試以下代碼梯嗽。
[car setValue: nil forKey:@"mileage"];
- 不過Cocoa會(huì)給出以下警告信息齿尽。
[<Car ox105740> setNilValueForKey]: could not set nil as the value for the key mileage.';
- 為了解決該問題,可以重寫項(xiàng)目-setNilValueForKey方法的實(shí)現(xiàn)灯节,并提供邏輯上有意義的任何值循头。我們先約定好,nil值表示行駛零距離炎疆,而不是像-1之類的其他值卡骂。
- (void) setNilValueForKey : (NSString *) key {
if([key isEqualToString:@"mileage"]){
mileage = 0;
}
else {
[super setNilValueForKey:key];
}
}
- 請(qǐng)注意,如果得到一個(gè)意料之外的鍵形入,我們將調(diào)用超類方法全跨。這樣的話,如果某人視圖對(duì)鍵/值編碼使用了我們不能理解的鍵唯笙,調(diào)用者將會(huì)得到警告信息螟蒸。一般來說盒使,除非你有某些特殊的原因(比如我不想執(zhí)行某個(gè)操作)崩掘,否則應(yīng)該總是在重寫的代碼中調(diào)用超類的方法。
7.處理未定義的鍵
- 如果你使用了KVC少办,并且輸入了錯(cuò)誤的鍵你可能會(huì)看到以下消息苞慢。
[<Car ox105740> valueForUnderfinedKey:]: this class is not key value coding-compliant for the key garbanzo.'
以上消息的主要含義是,Cocoa不能識(shí)別你使用的這個(gè)鍵英妓,因此放棄了操作挽放。
如果仔細(xì)分析錯(cuò)誤消息,你會(huì)注意到蔓纠,它提到了valueForUnderfinedKey:方法辑畦。你也許能夠猜到,我們可以通過重寫該方法來處理未定義的鍵腿倚。也許你還能猜到纯出,如果要更改未知鍵的值,還可以使用相應(yīng)的setValue:forUnderfine的Key:方法。
如果KVC機(jī)制無(wú)法找到處理方式暂筝,會(huì)退回并詢問類該如何處理箩言。默認(rèn)的實(shí)現(xiàn)會(huì)取消操作,就像你在前面所看到的焕襟。但是我們可以更改默認(rèn)的行為陨收,將Garage轉(zhuǎn)換成一個(gè)非常靈活的對(duì)象,通過它可以設(shè)置和獲取任何鍵鸵赖。我們首先添加一個(gè)可變字典务漩。
@interface Garage : NSObject {
NSString *name;
NSMutableArray *cars;
NSMutableDictionary *stuff;
}
...
@end
- 接下來添加valueForUnderfinedKey:方法的實(shí)現(xiàn)
- (void) setValue: (id) value forUndefinedKey: (NSString *) key {
if (stuff == nil) {
stuff = [[NSMutableDictionary alloc] init];
}
[stuff setValue: value forKey: key];
} // setValueForUndefinedKey
- (id) valueForUndefinedKey:(NSString *)key {
id value = [stuff valueForKey: key];
return (value);
} // valueForUndefinedKey
并在-dealloc方法中釋放字典。
現(xiàn)在可以設(shè)置garage對(duì)象上的任何值:
[garage setValue: @"bunny" forKey: @"fluffy"];
[garage setValue: @"greeble" forKey: @"bork"];
[garage setValue: [NSNull null] forKey: @"snorgle"];
[garage setValue: nil forKey: @"gronk"];
- 然后將它們的內(nèi)容輸出:
NSLog (@"values are %@ %@ %@ and %@",[garage valueForKey: @"fluffy"], [garage valueForKey: @"bork"],[garage valueForKey: @"snorgle"], [garage valueForKey: @"gronk"]);
- 這個(gè)NSLog將輸出以下結(jié)果:
values are bunny greeble <null> and (null)
- 請(qǐng)注意<null>和(null)之間的區(qū)別它褪。<null>是一種[NSNull null]對(duì)象菲饼,而(null)是一個(gè)真正的nil值。由于字典沒有鍵為gronk的值列赎,所以到此處我們得到了nil值宏悦。還要注意,在使用stuff字典時(shí)包吝,我們使用了KVC的setValue:forKey:方法饼煞。通過這種方法,調(diào)用者可以直接傳入nil值诗越,我們不必在代碼中檢查它砖瞧。而如果為NSDictionary類的setObjective:forKey:提供nil值,它將會(huì)給出警告信息嚷狞。此外块促,如果在字典中對(duì)setValue:forKey:方法傳入nil值,可能會(huì)把對(duì)應(yīng)鍵的值從字典中刪除床未。