iOS学习笔记03—Key-Value-Coding(KVC,键/值编码)
1、为什么苹果提供了KVC机制?什么是KVC?
答:简化代码,编程方便呀。
KVC的基本调用包括-valueForKey:和-setValue:forKey:。以字符串的形式向对象发送消息,这个字符串是我们关注的属性的关键信息。
看下面两段代码NSTableView委托方法的对比
1)不使用KVC的代码:
- (id)tableView:(NSTableView *)tableview
objectValueForTableColumn:(id)column row:(NSInteger)row {
ChildObject *child = [childrenArray objectAtIndex:row];
if ([[column identifier] isEqualToString:@"name"]) {
return [child name];
}
if ([[column identifier] isEqualToString:@"age"]) {
return [child age];
}
if ([[column identifier] isEqualToString:@"favoriteColor"]) {
return [child favoriteColor];
}
// And so on.
}
2)使用KVC的代码:
- (id)tableView:(NSTableView *)tableview
objectValueForTableColumn:(id)column row:(NSInteger)row {
ChildObject *child = [childrenArray objectAtIndex:row];
return [child valueForKey:[column identifier]];
}
2、KVC的缺点
KVC这么有用,为什么没有用它来处理所有对象,并抛弃存取方法?KVC有什么缺点呢?
答:因为KVC需要解析字符串来计算你需要的答案,因此速度比较慢。另外编译器无法对它进行错误检查。
3、对于KVC,Cocoa自动放入和取出标量值。
也就是说,当使用-setValue:forKey:时,它自动将标量值(int、float和struct)放入NSNumber或NSValue中;当使用-valueForKey:时,它自动将这些标量值从这些对象中取出。仅KVC具有这种自动包装功能,常规方法调用和属性语法不具备该功能。
如果在调用-setValue:forKey:之中设置一个标量值,需要将他包装起来。
比如
[car setValue: [NSNumber numberWithFloat: 25062.4] forKey: @”mileage”];
属性mileage是标量(float类型),所以要用NSNumber包装起来。
[car setValue: @”Harold” forKey: @”name”];
上面的代码功能是,它首先查找属性name的setter方法-setName,并使用参数@”Harold”调用它。如果不存在setter方法,它将在类中产找名为name或_name的实例变量,然后为它赋值。
题外话:编译器和苹果公司都以下划线开头的形式保存实例变量名称,如果你尝试在其他地方使用下划线,可能出现严重的错误。这条规则不是强制性的,但是如果不遵守它,你可能会遇到某种风险。
4、KVC还支持指定关键路径,可以遵循一系列关系来指定该路径。
比如
[car setValue: [NSNumber numberWithInt: 155]
forKeyPath: @”engine.horsepower”];
这些路径的深度是任意的,具体取决与对象图的复杂度。
5、KVC的整体操作,示例如下:
NSArray *pressures = [car valueForKeyPath: @”tires.pressure”];
6、KVC的集合操作:
NSNumber *count = [garage valueForKeyPath: @”cars.@count”];
cars用于获取cars属性,它是来自garage的NSArray类型的值。
@count,@意味着后面将进行一些运算,@count的意思是告诉KVC机制对键值路径左侧部分的结果进行计数操作。
还包括如下操作
Simple Collection Operators
@avg
@count
@max
@min
@sum
Object Operators
@distinctUnionOfObjects
@unionOfObjects
Array and Set Operators
@distinctUnionOfArrays
@unionOfArrays
@distinctUnionOfSets
7、完整示例
OK,讲了一大通,上面还是来个实际的例子吧,实际上就是Learn Objective-C on the Mac第16章的例子。
源码文件清单:Car.h, Car.m, Engine.h, Engine.m, Garage.h, Garage.m, Slant6.h, Slant6.m, Tire.h, Tire.m, main.m。
1)Car.h
#import <Cocoa/Cocoa.h>
@classTire;
@classEngine;
@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;
- (void) setTire: (Tire *) tire
atIndex: (int) index;
- (Tire *) tireAtIndex: (int) index;
- (void) print;
@end// Car
2)Car.m
#import "Car.h"
#import "Engine.h"
@implementation Car
@synthesize name;
@synthesize engine;
@synthesize make;
@synthesize model;
@synthesize modelYear;
@synthesize numberOfDoors;
@synthesize mileage;
- (id) init
{
if (self = [superinit]) {
self.name = @"Car";
tires = [[NSMutableArrayalloc]init];
int i;
for (i =0; i <4; i++) {
[tiresaddObject: [NSNullnull]];
}
}
return (self);
} // init
- (id) copyWithZone: (NSZone *) zone
{
Car *carCopy;
carCopy = [[[selfclass]
allocWithZone: zone]
init];
carCopy.name = name;
carCopy.make = make;
carCopy.model = model;
carCopy.numberOfDoors =numberOfDoors;
carCopy.mileage =mileage;
Engine *engineCopy;
engineCopy = [[enginecopy]autorelease];
carCopy.engine = engineCopy;
int i;
for (i = 0; i <4; i++) {
Tire *tireCopy;
tireCopy = [[selftireAtIndex: i]copy];
[tireCopyautorelease];
[carCopysetTire: tireCopy
atIndex: i];
}
return (carCopy);
} // copyWithZone
- (void) dealloc
{
self.name = nil;
[tiresrelease];
[enginerelease];
[superdealloc];
} // dealloc
- (void) setTire: (Tire *) tire
atIndex: (int) index
{
[tiresreplaceObjectAtIndex: index
withObject: tire];
} // setTire:atIndex:
- (Tire *) tireAtIndex: (int) index
{
Tire *tire;
tire = [tiresobjectAtIndex: index];
return (tire);
} // tireAtIndex:
- (void) print
{
NSString *desc = [selfdescription];
NSLog (@"%@", desc);
- (NSString *) description {
NSString *desc;
desc = [NSStringstringWithFormat:
@"%@, a %d %@ %@, has %d doors, %.1f miles, %d hp and %d tires",
name,modelYear,make,model,numberOfDoors,mileage, [enginehorsepower],
[tirescount]];
return desc;
} // description
- (void) setNilValueForKey: (NSString *) key {
if ([keyisEqualToString:@"mileage"]) {
mileage =0;
}else {
[supersetNilValueForKey: key];
}
} // setNilValueForKey
@end// Car
3)Engine.h
#import <Cocoa/Cocoa.h>
@interface Engine :NSObject <NSCopying> {
int horsepower;
}
@property (readwrite)int horsepower;
@end// Engine
4)Engine.m
#import "Engine.h"
@implementation Engine
@synthesize horsepower;
- (id) init {
if (self = [superinit]) {
horsepower =145;
}
return (self);
} // init
- (id) copyWithZone: (NSZone *) zone
{
Engine *engineCopy;
engineCopy = [[[selfclass]
allocWithZone: zone]
init];
[engineCopysetValue: [NSNumbernumberWithInt:horsepower]
forKey:@"horsepower"];
return (engineCopy);
} // copyWithZone
- (NSString *) description
{
NSString *description;
description = [NSStringstringWithFormat:
@"I am an engine. Vrooom! %d HP",horsepower];
return (description);
} // description
@end// Engine
5)Garage.h
//
// Garage.h
// 15.01 Car-Value-Coding
//
// Created by markd on 8/16/08.
// Copyright 2008 Length-O-Words.com. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@classCar;
@interface Garage :NSObject {
NSString *name;
NSMutableArray *cars;
NSMutableDictionary *stuff;
}
@property (readwrite,copy)NSString *name;
- (void) addCar: (Car *) car;
- (void) print;
@end// Garage
6)Garage.m
//
// Garage.m
// 15.01 Car-Value-Coding
//
// Created by markd on 8/16/08.
// Copyright 2008 Length-O-Words.com. All rights reserved.
//
#import "Garage.h"
@implementation Garage
@synthesize name;
- (void) addCar: (Car *) car {
if (cars == nil) {
cars = [[NSMutableArrayalloc]init];
}
[carsaddObject: car];
} // addCar
- (void) dealloc {
[namerelease];
[carsrelease];
[stuffrelease];
[superdealloc];
} // dealloc
- (void) print {
NSLog (@"%@:",name);
for (Car *car incars) {
NSLog (@" %@", car);
}
- (void) setValue: (id) value forUndefinedKey: (NSString *) key {
if (stuff == nil) {
stuff = [[NSMutableDictionaryalloc]init];
}
[stuffsetValue: valueforKey: key];
} // setValueForUndefinedKey
- (id) valueForUndefinedKey:(NSString *)key {
id value = [stuffvalueForKey: key];
return (value);
} // valueForUndefinedKey
@end // Car
7)Slant6.h
#import "Engine.h"
@interface Slant6 :Engine
@end// Slant6
8)Slant6.m
#import "Slant6.h"
@implementation Slant6
- (NSString *) description
{
return (@"I am a slant-6. VROOOM!");
} // description
@end// Slant6
9)Tire.h
#import <Cocoa/Cocoa.h>
@interface Tire :NSObject <NSCopying> {
float pressure;
float treadDepth;
}
@propertyfloat pressure;
@propertyfloat treadDepth;
- (id) initWithPressure: (float) pressure;
- (id) initWithTreadDepth: (float) treadDepth;
- (id) initWithPressure: (float) pressure
treadDepth: (float) treadDepth;
@end// Tire
10)Tire.m
#import "Tire.h"
@implementation Tire
@synthesize pressure;
@synthesize treadDepth;
- (id) init
{
if (self = [selfinitWithPressure:34
treadDepth:20]) {
}
return (self);
} // init
- (id) copyWithZone: (NSZone *) zone
{
Tire *tireCopy;
tireCopy = [[[selfclass]allocWithZone: zone]
initWithPressure:pressure
treadDepth:treadDepth];
return (tireCopy);
} // copyWithZone
- (id) initWithPressure: (float) p
{
if (self = [selfinitWithPressure: p
treadDepth:20.0]) {
}
return (self);
} // initWithPressure
- (id) initWithTreadDepth: (float) td
{
if (self = [selfinitWithPressure:34.0
treadDepth: td]) {
}
return (self);
} // initWithTreadDepth
- (id) initWithPressure: (float) p
treadDepth: (float) td
{
if (self = [superinit]) {
pressure = p;
treadDepth = td;
}
return (self);
} // initWithPressure:treadDepth:
- (NSString *) description
{
NSString *desc;
desc = [NSStringstringWithFormat:
@"Tire: Pressure: %.1f TreadDepth: %.1f",
pressure,treadDepth];
return (desc);
} // description
@end// Tire
11)main.m
#import <Foundation/Foundation.h>
#import "Car.h"
#import "Engine.h"
#import "Garage.h"
#import "Slant6.h"
#import "Tire.h"
Car *makeCar (NSString *name, NSString *make,NSString *model,
int modelYear,int numberOfDoors,float mileage,
int horsepower) {
Car *car = [[[Caralloc]init]autorelease];
car.name = name;
car.make = make;
car.model = model;
car.modelYear = modelYear;
car.numberOfDoors = numberOfDoors;
car.mileage = mileage;
Slant6 *engine = [[[Slant6alloc]init]autorelease];
[enginesetValue: [NSNumbernumberWithInt: horsepower]
forKey:@"horsepower"];
car.engine = engine;
// Make some tires.
// int i;
for (int i = 0; i <4; i++) {
Tire * tire= [[[Tirealloc]init]autorelease];
[carsetTire: tire atIndex: i];
}
return (car);
} // makeCar
int main (int argc, constchar * argv[])
{
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePoolalloc]init];
Garage *garage = [[Garagealloc]init];
garage.name =@"Joe's Garage";
Car *car;
car =makeCar (@"Herbie",@"Honda",@"CRX",1984,2,110000,58);
[garageaddCar: car];
car =makeCar (@"Badger",@"Acura",@"Integra",1987,5,217036.7,130);
[garageaddCar: car];
car =makeCar (@"Elvis",@"Acura",@"Legend",1989,4,28123.4,151);
[garageaddCar: car];
car =makeCar (@"Phoenix",@"Pontiac",@"Firebird",1969,2,85128.3,345);
[garageaddCar: car];
car =makeCar (@"Streaker",@"Pontiac",@"Silver Streak",1950,2,39100.0,36);
[garageaddCar: car];
car =makeCar (@"Judge",@"Pontiac",@"GTO",1969,2,45132.2,370);
[garageaddCar: car];
car =makeCar (@"Paper Car",@"Plymouth",@"Valiant",1965,2,76800,105);
[garageaddCar: car];
[garageprint];
NSNumber *count;
count = [garagevalueForKeyPath:@"cars.@count"];
NSLog (@"We have %@ cars", count);
NSNumber *sum;
sum = [garagevalueForKeyPath:@"[email protected]"];
NSLog (@"We have a grand total of %@ miles", sum);
NSNumber *avgMileage;
avgMileage = [garagevalueForKeyPath:@"[email protected]"];
NSLog (@"average is %.2f", [avgMileagefloatValue]);
NSNumber *min, *max;
min = [garagevalueForKeyPath:@"[email protected]"];
max = [garagevalueForKeyPath:@"[email protected]"];
NSLog (@"minimax: %@ / %@", min, max);
NSArray *manufacturers;
manufacturers = [garagevalueForKeyPath:@"[email protected]"];
NSLog (@"makers: %@", manufacturers);
car = [[garagevalueForKeyPath:@"cars"]lastObject];
NSArray *keys = [NSArrayarrayWithObjects:@"make",@"model",@"modelYear",nil];
NSDictionary *carValues = [car dictionaryWithValuesForKeys: keys];
NSLog (@"Car values : %@", carValues);
NSDictionary *newValues =
[NSDictionarydictionaryWithObjectsAndKeys:
@"Chevy",@"make",
@"Nova",@"model",
[NSNumbernumberWithInt:1964],@"modelYear",
[NSNullnull],@"mileage",
nil];
[carsetValuesForKeysWithDictionary: newValues];
NSLog (@"car with new values is %@", car);
[carsetValue:nilforKey:@"mileage"];
NSLog (@"Nil miles are %@", car.mileage);
[garagesetValue:@"bunny"forKey:@"fluffy"];
[garagesetValue:@"greeble"forKey:@"bork"];
[garagesetValue: [NSNullnull]forKey:@"snorgle"];
[garagesetValue:nilforKey:@"gronk"];
NSLog (@"values are %@ %@ %@ and %@",
[garagevalueForKey:@"fluffy"],
[garagevalueForKey:@"bork"],
[garagevalueForKey:@"snorgle"],
[garagevalueForKey:@"gronk"]);
NSLog (@"%@", [garagevalueForKey:@"stuff"]);
[garagerelease];
[poolrelease];
return (0);
} // main
8、输出结果
2013-01-09 09:01:05.408 16.02 Car-Value-Garaging[424:803] Joe's Garage:
2013-01-09 09:01:05.409 16.02 Car-Value-Garaging[424:803] Herbie, a 1984 Honda CRX, has 2 doors, 110000.0 miles, 58 hp and 4 tires
2013-01-09 09:01:05.410 16.02 Car-Value-Garaging[424:803] Badger, a 1987 Acura Integra, has 5 doors, 217036.7 miles, 130 hp and 4 tires
2013-01-09 09:01:05.411 16.02 Car-Value-Garaging[424:803] Elvis, a 1989 Acura Legend, has 4 doors, 28123.4 miles, 151 hp and 4 tires
2013-01-09 09:01:05.411 16.02 Car-Value-Garaging[424:803] Phoenix, a 1969 Pontiac Firebird, has 2 doors, 85128.3 miles, 345 hp and 4 tires
2013-01-09 09:01:05.412 16.02 Car-Value-Garaging[424:803] Streaker, a 1950 Pontiac Silver Streak, has 2 doors, 39100.0 miles, 36 hp and 4 tires
2013-01-09 09:01:05.412 16.02 Car-Value-Garaging[424:803] Judge, a 1969 Pontiac GTO, has 2 doors, 45132.2 miles, 370 hp and 4 tires
2013-01-09 09:01:05.413 16.02 Car-Value-Garaging[424:803] Paper Car, a 1965 Plymouth Valiant, has 2 doors, 76800.0 miles, 105 hp and 4 tires
2013-01-09 09:01:05.414 16.02 Car-Value-Garaging[424:803] We have 7 cars
2013-01-09 09:01:05.414 16.02 Car-Value-Garaging[424:803] We have a grand total of 601320.6 miles
2013-01-09 09:01:05.415 16.02 Car-Value-Garaging[424:803] average is 85902.95
2013-01-09 09:01:05.415 16.02 Car-Value-Garaging[424:803] minimax: 28123.4 / 217036.7
2013-01-09 09:01:05.416 16.02 Car-Value-Garaging[424:803] makers: (
Pontiac,
Acura,
Plymouth,
Honda
)
2013-01-09 09:01:05.417 16.02 Car-Value-Garaging[424:803] Car values : {
make = Plymouth;
model = Valiant;
modelYear = 1965;
}
2013-01-09 09:01:05.417 16.02 Car-Value-Garaging[424:803] car with new values is Paper Car, a 1964 Chevy Nova, has 2 doors, 0.0 miles, 105 hp and 4 tires
2013-01-09 09:01:05.418 16.02 Car-Value-Garaging[424:803] Nil miles are (null)
2013-01-09 09:01:05.418 16.02 Car-Value-Garaging[424:803] values are bunny greeble <null> and (null)
2013-01-09 09:01:05.419 16.02 Car-Value-Garaging[424:803] {
bork = greeble;
fluffy = bunny;
snorgle = "<null>";
}