文件加载与保存及键值编码技术解析
1. 文件加载与保存
1.1 编码与解码
编码和解码的工作方式是相同的,以下是示例代码:
freezeDried = [NSKeyedArchiver archivedDataWithRootObject: thing1];
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData: freezeDried];
NSLog (@"reconstituted multithing: %@", thing1);
当数据中存在循环时,例如
thing1
在其自身的
subThingies
数组中,Cocoa 的归档器和反归档器实现得很巧妙,能够处理对象循环的保存和恢复。不过,不能对
thing1
使用
NSLog
,因为
NSLog
无法检测对象循环,会导致无限递归。
1.2 加载和保存文件的技术
Cocoa 提供了两种加载和保存文件的技术:
-
属性列表(plists)
:属性列表数据类型是一组知道如何加载和保存自身的类。如果对象集合都是属性列表类型,可以使用便捷函数将它们保存到磁盘并读取回来。
-
对象编码
:如果有非属性列表类型的自定义对象,可以采用
NSCoding
协议并实现编码和解码方法,将对象转换为
NSData
,保存到磁盘后再读取并重建对象。
2. 键值编码(KVC)
2.1 简介
键值编码(KVC)是一种间接更改对象状态的方法,通过字符串描述要更改的对象状态部分。一些高级的 Cocoa 特性,如 Core Data 和 Cocoa Bindings,会使用 KVC 作为基础机制。
2.2 示例项目
以
Car
类为例,添加了一些属性,如
make
和
model
,并更新了相关方法。以下是
Car
类的部分代码:
@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
@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;
// plus copying tires and engine, code in chapter 13.
return carCopy;
}
- (NSString *) description
{
NSString *desc;
desc = [NSString stringWithFormat:
@"%@, a %d %@ %@, has %d doors, %.1f miles, and %d tires.", name, modelYear, make, model,
numberOfDoors, mileage, [tires count]];
return desc;
}
@end
在
main
函数中设置
Car
对象的属性并打印:
int main (int argc, const char * argv[])
{
@autoreleasepool
{
Car *car = [[[Car alloc] init] autorelease];
car.name = @"Herbie";
car.make = @"Honda";
car.model = @"CRX";
car.numberOfDoors = 2;
car.modelYear = 1984;
car.mileage = 110000;
int i;
for (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);
}
return (0);
}
2.3 KVC 基本调用
KVC 的基本调用方法是
-valueForKey:
和
-setValue:forKey:
。以下是示例代码:
NSString *name = [car valueForKey:@"name"];
NSLog (@"%@", name);
NSLog (@"make is %@", [car valueForKey:@"make"]);
-valueForKey:
首先查找以键命名的 getter 方法,如果没有则查找实例变量。对于标量值,KVC 会自动进行装箱和拆箱操作。设置值的示例代码如下:
[car setValue: @"Harold" forKey: @"name"];
[car setValue: [NSNumber numberWithFloat: 25062.4] forKey: @"mileage"];
2.4 键路径
键值编码允许指定键路径,类似于文件系统路径,可用于跟踪对象关系链。例如,为
Engine
类添加
horsepower
实例变量:
@interface Engine : NSObject <NSCopying>
{
int horsepower;
}
@end
- (id) init
{
if (self = [super init])
{
horsepower = 145;
}
return (self);
}
通过键路径访问和设置值的示例代码:
NSLog (@"horsepower is %@", [engine valueForKey: @"horsepower"]);
[engine setValue: [NSNumber numberWithInt: 150]forKey: @"horsepower"];
NSLog (@"horsepower is %@", [engine valueForKey: @"horsepower"]);
[car setValue: [NSNumber numberWithInt: 155]forKeyPath: @"engine.horsepower"];
NSLog (@"horsepower is %@", [car valueForKeyPath: @"engine.horsepower"]);
2.5 聚合操作
KVC 的一个很酷的特性是,如果向
NSArray
请求一个键的值,它会向数组中的每个对象请求该键的值,并将结果打包成另一个数组返回。例如,获取汽车所有轮胎的气压:
NSArray *pressures = [car valueForKeyPath: @"tires.pressure"];
NSLog (@"pressures %@", pressures);
2.6 车库类示例
创建了
Garage
类来存储经典汽车,以下是
Garage
类的代码:
@interface Garage : NSObject
{
NSString *name;
NSMutableArray *cars;
}
@property (readwrite, copy) NSString *name;
- (void) addCar: (Car *) car;
- (void) print;
@end
@implementation Garage
@synthesize name;
- (void) addCar: (Car *) car
{
if (cars == nil)
{
cars = [[NSMutableArray alloc] init];
}
[cars addObject: car];
}
- (void) dealloc
{
[name release];
[cars release];
[super dealloc];
}
- (void) print
{
NSLog (@"%@:", name);
for (Car *car in cars)
{
NSLog (@" %@", car);
}
}
@end
在
main
函数中创建车库并添加汽车:
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);
}
2.7 键路径操作符
键路径可以包含操作符,如
@count
、
@sum
、
@avg
、
@min
和
@max
。以下是一些示例:
NSNumber *count;
count = [garage valueForKeyPath: @"cars.@count"];
NSLog (@"We have %@ cars", count);
NSNumber *sum;
sum = [garage valueForKeyPath: @"cars.@sum.mileage"];
NSLog (@"We have a grand total of %@ miles", sum);
NSNumber *avgMileage;
avgMileage = [garage valueForKeyPath: @"cars.@avg.mileage"];
NSLog (@"average is %.2f", [avgMileage floatValue]);
NSNumber *min, *max;
min = [garage valueForKeyPath: @"cars.@min.mileage"];
max = [garage valueForKeyPath: @"cars.@max.mileage"];
NSLog (@"minimax: %@ / %@", min, max);
操作符功能总结
| 操作符 | 功能 | 示例 |
|---|---|---|
| @count | 计算数组元素数量 |
[garage valueForKeyPath: @"cars.@count"]
|
| @sum | 计算数组中指定键值的总和 |
[garage valueForKeyPath: @"cars.@sum.mileage"]
|
| @avg | 计算数组中指定键值的平均值 |
[garage valueForKeyPath: @"cars.@avg.mileage"]
|
| @min | 找出数组中指定键值的最小值 |
[garage valueForKeyPath: @"cars.@min.mileage"]
|
| @max | 找出数组中指定键值的最大值 |
[garage valueForKeyPath: @"cars.@max.mileage"]
|
键值编码流程简图
graph LR
A[对象] --> B[-valueForKey:或 -setValue:forKey:]
B --> C{是否有对应 getter/setter 方法}
C -- 有 --> D[调用方法]
C -- 无 --> E{是否有对应实例变量}
E -- 有 --> F[访问实例变量]
E -- 无 --> G[无结果]
通过以上内容,我们了解了文件加载与保存的编码解码技术,以及键值编码的基本概念、操作方法和高级特性,包括键路径和操作符的使用。这些技术在 Cocoa 开发中非常有用,可以提高代码的灵活性和可维护性。
3. 键值编码的实际应用场景
3.1 数据统计与分析
在实际开发中,我们经常需要对一组对象的数据进行统计和分析。键值编码的操作符提供了便捷的方式来完成这些任务。例如,在车库管理系统中,我们可以快速计算汽车的数量、总里程、平均里程等信息。以下是一个简单的流程说明:
1.
获取对象集合
:从车库中获取汽车对象的数组,通过
garage.cars
即可得到。
2.
选择合适的操作符
:根据需求选择
@count
、
@sum
、
@avg
、
@min
或
@max
操作符。
3.
构建键路径
:将操作符与具体的属性名组合成键路径,如
@"cars.@sum.mileage"
。
4.
调用
valueForKeyPath:
方法
:将键路径作为参数传递给
valueForKeyPath:
方法,获取统计结果。
3.2 动态属性访问
有时候,我们需要在运行时动态地访问对象的属性。键值编码允许我们通过字符串来指定要访问的属性,而不需要在编译时确定。例如,我们可以根据用户输入的属性名来获取汽车的相关信息:
NSString *propertyName = @"modelYear";
NSNumber *modelYear = [car valueForKey:propertyName];
NSLog(@"The model year of the car is %@", modelYear);
3.3 数据绑定
在一些界面开发中,我们需要将数据模型与界面元素进行绑定,当数据模型发生变化时,界面元素能够自动更新。键值编码可以用于实现这种数据绑定机制。以下是一个简单的示例:
// 假设我们有一个 UILabel 用于显示汽车的名称
UILabel *carNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)];
// 绑定汽车的 name 属性到 UILabel 的 text 属性
[car addObserver:carNameLabel forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
在上述示例中,当汽车的
name
属性发生变化时,
UILabel
的
text
属性会自动更新。
4. 注意事项与最佳实践
4.1 避免使用以下划线开头的实例变量
编译器和 Apple 保留了以下划线开头的实例变量名,虽然目前没有强制禁止使用,但为了避免潜在的问题,建议不要使用。
4.2 处理标量值的装箱和拆箱
在使用 KVC 处理标量值(如
int
、
float
等)时,需要注意自动装箱和拆箱操作。在设置值时,要将标量值包装成
NSNumber
或
NSValue
对象;在获取值时,要进行相应的拆箱操作。
4.3 异常处理
在使用键值编码时,可能会出现一些异常情况,如键不存在或对象不支持该键。为了避免程序崩溃,建议在代码中进行异常处理。例如:
@try {
NSString *name = [car valueForKey:@"unknownKey"];
} @catch (NSException *exception) {
NSLog(@"An exception occurred: %@", exception.reason);
}
4.4 性能考虑
虽然键值编码提供了便捷的方式来访问对象的属性,但相比于直接访问实例变量或调用方法,它的性能会稍低一些。在对性能要求较高的场景中,建议使用直接访问的方式。
5. 总结
5.1 技术要点回顾
-
文件加载与保存
:Cocoa 提供了属性列表和对象编码两种技术来加载和保存文件。对象编码通过
NSKeyedArchiver和NSKeyedUnarchiver实现,能够处理对象循环的保存和恢复。 -
键值编码
:KVC 是一种间接更改对象状态的方法,通过
-valueForKey:和-setValue:forKey:方法进行基本调用。键路径允许我们跟踪对象关系链,操作符提供了强大的聚合功能。
5.2 应用价值
这些技术在 Cocoa 开发中具有重要的应用价值,能够提高代码的灵活性和可维护性。通过键值编码,我们可以在运行时动态地访问和修改对象的属性,实现数据统计和分析,以及数据绑定等功能。
5.3 未来展望
随着 Cocoa 框架的不断发展,键值编码可能会有更多的应用场景和优化。开发者可以进一步探索这些技术,结合实际需求,开发出更加高效、灵活的应用程序。
键值编码应用场景总结
| 应用场景 | 操作步骤 | 示例代码 |
|---|---|---|
| 数据统计与分析 |
1. 获取对象集合;2. 选择操作符;3. 构建键路径;4. 调用
valueForKeyPath:
方法
|
NSNumber *sum = [garage valueForKeyPath: @"cars.@sum.mileage"];
|
| 动态属性访问 |
1. 确定属性名;2. 调用
valueForKey:
方法
|
NSNumber *modelYear = [car valueForKey:propertyName];
|
| 数据绑定 | 1. 创建界面元素;2. 注册观察者;3. 监听属性变化 |
[car addObserver:carNameLabel forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
|
键值编码使用注意事项流程图
graph LR
A[开始使用 KVC] --> B{是否使用下划线开头的实例变量}
B -- 是 --> C[避免使用,更换名称]
B -- 否 --> D{是否处理标量值}
D -- 是 --> E[进行装箱和拆箱操作]
D -- 否 --> F{是否需要异常处理}
F -- 是 --> G[添加异常处理代码]
F -- 否 --> H{是否对性能要求高}
H -- 是 --> I[考虑直接访问方式]
H -- 否 --> J[正常使用 KVC]
C --> J
E --> J
G --> J
J --> K[结束]
通过对键值编码的深入学习和实践,我们可以更好地利用这些技术来解决实际开发中的问题,提高开发效率和代码质量。
超级会员免费看

被折叠的 条评论
为什么被折叠?



