第十八章、鍵/值編碼

  • 許多編程思想基于間接機(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)鍵的值從字典中刪除床未。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竭翠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子薇搁,更是在濱河造成了極大的恐慌浩聋,老刑警劉巖挖胃,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件默伍,死亡現(xiàn)場(chǎng)離奇詭異惫东,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宏娄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門问裕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孵坚,你說我怎么就攤上這事粮宛∶蔡ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵窟勃,是天一觀的道長(zhǎng)祖乳。 經(jīng)常有香客問我,道長(zhǎng)秉氧,這世上最難降的妖魔是什么眷昆? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮汁咏,結(jié)果婚禮上亚斋,老公的妹妹穿的比我還像新娘。我一直安慰自己攘滩,他們只是感情好帅刊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漂问,像睡著了一般赖瞒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚤假,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天栏饮,我揣著相機(jī)與錄音,去河邊找鬼磷仰。 笑死袍嬉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灶平。 我是一名探鬼主播伺通,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼逢享!你這毒婦竟也來了罐监?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拼苍,失蹤者是張志新(化名)和其女友劉穎笑诅,沒想到半個(gè)月后调缨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疮鲫,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年弦叶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俊犯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伤哺,死狀恐怖燕侠,靈堂內(nèi)的尸體忽然破棺而出者祖,到底是詐尸還是另有隱情,我是刑警寧澤绢彤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布七问,位于F島的核電站,受9級(jí)特大地震影響茫舶,放射性物質(zhì)發(fā)生泄漏械巡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一饶氏、第九天 我趴在偏房一處隱蔽的房頂上張望讥耗。 院中可真熱鬧,春花似錦疹启、人聲如沸古程。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挣磨。三九已至,卻和暖如春荤懂,著一層夾襖步出監(jiān)牢的瞬間趋急,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工势誊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呜达,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓粟耻,卻偏偏與公主長(zhǎng)得像查近,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挤忙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 37.cocoa內(nèi)存管理規(guī)則 1)當(dāng)你使用new霜威,alloc或copy方法創(chuàng)建一個(gè)對(duì)象時(shí),該對(duì)象的保留計(jì)數(shù)器值為1...
    如風(fēng)家的秘密閱讀 842評(píng)論 0 4
  • 關(guān)于鍵值編碼 鍵值編碼(KVC)是一種由NSKeyValueCoding非正式協(xié)議提供的機(jī)制册烈,對(duì)象采用該機(jī)制來提供...
    漸z閱讀 926評(píng)論 0 0
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,097評(píng)論 1 32
  • Promise 類似于一個(gè)事務(wù)管理器戈泼,它的作用就是將各種內(nèi)嵌回調(diào)的事務(wù)用流水形式表達(dá)。利用 Promise 可以讓...
    紫陌蘭溪閱讀 508評(píng)論 0 0
  • 隨著社會(huì)生產(chǎn)力的發(fā)展赏僧。我們步入一個(gè)新的時(shí)代大猛。我們每個(gè)人都應(yīng)該不忘初心,努力工作淀零,在自己平凡的崗位上做出應(yīng)有...
    C組2號(hào)閱讀 282評(píng)論 0 0