文件加载与保存及键值编码全解析
1. 文件加载与保存
在数据处理过程中,文件的加载和保存是常见需求。这里主要介绍两种技术:属性列表(plists)和对象编码。
属性列表数据类型是一类知道如何自行加载和保存的类集合。若对象集合均为属性列表类型,可使用便捷函数将其保存到磁盘并读取回来。例如,对于一些简单的配置信息,就可以使用属性列表来存储和读取。
而对于非属性列表类型的自定义对象,可采用NSCoding协议并实现编码和解码方法。这样就能将自定义对象转换为NSData,保存到磁盘后后续再读取并重构对象。
在编码和解码操作中,示例代码如下:
freezeDried = [NSKeyedArchiver archivedDataWithRootObject: thing1];
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData: freezeDried];
NSLog (@"reconstituted multithing: %@", thing1);
当编码的数据存在循环引用时,比如
thing1
在其自身的
subThingies
数组中,Cocoa的归档器和解档器实现很巧妙,能正常保存和恢复对象循环。不过,若使用
NSLog
打印
thing1
,由于
NSLog
无法检测对象循环,会陷入无限递归。
2. 键值编码(KVC)
键值编码是一种间接改变对象状态的方式,通过字符串描述要改变的对象状态部分。一些高级的Cocoa特性,如Core Data和Cocoa Bindings,会将KVC作为基础机制。
2.1 入门项目
以
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);
}
运行程序后,输出如下:
Car is Herbie, a 1984 Honda CRX, has 2 doors, 110000.0 miles, and 4 tires.
2.2 KVC基础调用
KVC的基本调用方法是
-valueForKey:
和
-setValue:forKey:
。通过向对象发送消息并传入字符串(即感兴趣属性的键)来操作。
例如,获取汽车的名称和品牌:
NSString *name = [car valueForKey:@"name"];
NSLog (@"%@", name);
NSLog (@"make is %@", [car valueForKey:@"make"]);
-valueForKey:
方法首先查找以键命名的getter方法(如
-key
或
-isKey
),若没有则查找名为
_key
或
key
的实例变量。
在设置值时,使用
-setValue:forKey:
方法。若设置标量值,需先将其包装:
[car setValue: @"Harold" forKey: @"name"];
[car setValue: [NSNumber numberWithFloat: 25062.4] forKey: @"mileage"];
2.3 键路径
键值编码允许指定键路径,类似于文件系统路径,可遵循一系列关系。例如,为
Engine
类添加
horsepower
实例变量:
@interface Engine : NSObject <NSCopying>
{
int horsepower;
}
@end
@implementation Engine
- (id) init
{
if (self = [super init])
{
horsepower = 145;
}
return (self);
}
@end
通过键路径获取和设置
horsepower
值:
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.4 聚合操作
KVC的一个强大特性是,当向
NSArray
请求某个键的值时,它会向数组中的每个对象请求该键的值,并将结果打包成另一个数组返回。例如,获取汽车所有轮胎的气压:
NSArray *pressures = [car valueForKeyPath: @"tires.pressure"];
NSLog (@"pressures %@", pressures);
不过,不能在键路径中对数组进行索引,如
"tires[0].pressure"
。
3. 车库类的引入
为进一步演示KVC的功能,引入
Garage
类,用于存放经典汽车。以下是
Garage
类的代码:
#import <Cocoa/Cocoa.h>
@class Car;
@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];
// 其他汽车添加代码...
[garage print];
[garage release];
}
return (0);
}
运行程序后,输出车库中的汽车信息:
Joe's Garage:
Herbie, a 1984 Honda CRX, has 2 doors, 110000.0 miles, 58 hp and 4 tires
Badger, a 1987 Acura Integra, has 5 doors, 217036.7 miles, 130 hp and 4 tires
// 其他汽车信息...
4. 键路径操作符
键路径不仅可以引用对象值,还可以使用操作符执行一些特殊操作,如计算数组值的平均值、最小值和最大值。
| 操作符 | 功能 | 示例 |
|---|---|---|
@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"]
|
例如,计算车库中汽车的数量、总里程、平均里程、最小里程和最大里程:
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);
通过上述内容,我们可以清晰地看到文件加载与保存以及键值编码在实际编程中的应用和强大功能。无论是简单的对象操作还是复杂的数据统计,都能通过这些技术高效完成。
graph LR
A[文件加载与保存] --> B[属性列表]
A --> C[对象编码]
D[键值编码] --> E[基础调用]
D --> F[键路径]
D --> G[聚合操作]
D --> H[键路径操作符]
I[Garage类] --> J[添加汽车]
I --> K[打印汽车信息]
文件加载与保存及键值编码全解析
5. 操作符详细解析
键路径操作符为数据处理带来了极大的便利,下面对几个常用操作符进行详细解析。
5.1
@count
操作符
@count
操作符用于计算数组元素的数量。在前面的例子中,我们通过以下代码计算车库中汽车的数量:
NSNumber *count;
count = [garage valueForKeyPath: @"cars.@count"];
NSLog (@"We have %@ cars", count);
操作步骤如下:
1.
valueForKeyPath:
方法接收到
"cars.@count"
这个键路径。
2. 首先获取
garage
对象的
cars
属性,该属性是一个
NSArray
。
3.
@count
操作符对
cars
数组进行计数,并将结果封装在
NSNumber
对象中返回。
5.2
@sum
操作符
@sum
操作符用于计算数组中特定属性值的总和。例如,计算车库中所有汽车的总里程:
NSNumber *sum;
sum = [garage valueForKeyPath: @"cars.@sum.mileage"];
NSLog (@"We have a grand total of %@ miles", sum);
操作步骤如下:
1.
valueForKeyPath:
方法接收到
"cars.@sum.mileage"
键路径。
2. 先获取
garage
对象的
cars
属性,得到一个
NSArray
。
3.
@sum
操作符将
mileage
键路径应用到
cars
数组中的每个元素上,获取每个汽车的里程值。
4. 将所有里程值相加,并将结果封装在
NSNumber
对象中返回。
5.3
@avg
操作符
@avg
操作符用于计算数组中特定属性值的平均值。计算车库中汽车的平均里程:
NSNumber *avgMileage;
avgMileage = [garage valueForKeyPath: @"cars.@avg.mileage"];
NSLog (@"average is %.2f", [avgMileage floatValue]);
操作步骤如下:
1.
valueForKeyPath:
方法接收到
"cars.@avg.mileage"
键路径。
2. 同样先获取
garage
对象的
cars
属性。
3.
@avg
操作符将
mileage
键路径应用到
cars
数组中的每个元素上,获取每个汽车的里程值。
4. 计算所有里程值的总和,并除以汽车的数量,将结果封装在
NSNumber
对象中返回。
5.4
@min
和
@max
操作符
@min
和
@max
操作符分别用于获取数组中特定属性值的最小值和最大值。获取车库中汽车里程的最小值和最大值:
NSNumber *min, *max;
min = [garage valueForKeyPath: @"cars.@min.mileage"];
max = [garage valueForKeyPath: @"cars.@max.mileage"];
NSLog (@"minimax: %@ / %@", min, max);
操作步骤与
@sum
和
@avg
类似,只是最终返回的是最小值或最大值。
6. 总结与应用场景
通过前面的介绍,我们了解了文件加载与保存的两种技术(属性列表和对象编码)以及键值编码的多种应用。下面总结一下这些技术的应用场景。
| 技术 | 应用场景 |
|---|---|
| 属性列表 | 适用于简单配置信息的存储和读取,如应用的设置、用户偏好等。这些信息通常由基本的数据类型组成,如字符串、数字、数组等。 |
| 对象编码 |
当需要存储和恢复自定义对象时使用。通过实现
NSCoding
协议,可以将对象转换为
NSData
进行持久化存储。
|
| 键值编码 | |
| - 基础调用 | 当需要动态获取或设置对象的属性时,通过字符串键来操作,提高代码的灵活性。 |
| - 键路径 | 处理对象之间的嵌套关系,通过键路径可以方便地访问和修改深层次的属性。 |
| - 聚合操作 | 对数组中的对象进行批量操作,如获取所有对象的某个属性值。 |
| - 键路径操作符 | 进行数据统计和分析,如计算数组元素的数量、总和、平均值、最小值和最大值等。 |
7. 注意事项
在使用这些技术时,还需要注意以下几点:
1.
对象循环引用
:在进行对象编码时,如果存在对象循环引用,虽然Cocoa的归档器和解档器可以处理,但使用
NSLog
打印可能会导致无限递归。
2.
实例变量命名
:编译器和苹果保留以下划线开头的实例变量名,使用时可能会带来潜在风险,应避免使用。
3.
标量值处理
:在使用键值编码设置标量值时,需要先将其包装成
NSNumber
或
NSValue
对象;获取时,Cocoa会自动进行拆箱操作。
8. 示例代码综合应用
下面是一个综合应用上述技术的示例代码,展示如何创建对象、进行文件保存和读取,以及使用键值编码进行数据操作。
#import <Foundation/Foundation.h>
// Car类定义
@interface Car : NSObject <NSCoding>
{
NSString *name;
NSString *make;
NSString *model;
int modelYear;
int numberOfDoors;
float mileage;
}
@property (readwrite, copy) NSString *name;
@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 make;
@synthesize model;
@synthesize modelYear;
@synthesize numberOfDoors;
@synthesize mileage;
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:name forKey:@"name"];
[aCoder encodeObject:make forKey:@"make"];
[aCoder encodeObject:model forKey:@"model"];
[aCoder encodeInt:modelYear forKey:@"modelYear"];
[aCoder encodeInt:numberOfDoors forKey:@"numberOfDoors"];
[aCoder encodeFloat:mileage forKey:@"mileage"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
name = [aDecoder decodeObjectForKey:@"name"];
make = [aDecoder decodeObjectForKey:@"make"];
model = [aDecoder decodeObjectForKey:@"model"];
modelYear = [aDecoder decodeIntForKey:@"modelYear"];
numberOfDoors = [aDecoder decodeIntForKey:@"numberOfDoors"];
mileage = [aDecoder decodeFloatForKey:@"mileage"];
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@, a %d %@ %@, has %d doors, %.1f miles.", name, modelYear, make, model, numberOfDoors, mileage];
}
@end
// Garage类定义
@interface Garage : NSObject
{
NSString *name;
NSMutableArray *cars;
}
@property (readwrite, copy) NSString *name;
- (void)addCar:(Car *)car;
- (void)print;
- (void)saveToFile:(NSString *)filePath;
- (instancetype)initWithFile:(NSString *)filePath;
@end
@implementation Garage
@synthesize name;
- (instancetype)init {
self = [super init];
if (self) {
cars = [NSMutableArray array];
}
return self;
}
- (void)addCar:(Car *)car {
[cars addObject:car];
}
- (void)print {
NSLog (@"%@:", name);
for (Car *car in cars) {
NSLog (@" %@", car);
}
}
- (void)saveToFile:(NSString *)filePath {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:cars];
[data writeToFile:filePath atomically:YES];
}
- (instancetype)initWithFile:(NSString *)filePath {
self = [super init];
if (self) {
NSData *data = [NSData dataWithContentsOfFile:filePath];
if (data) {
cars = [NSKeyedUnarchiver unarchiveObjectWithData:data];
} else {
cars = [NSMutableArray array];
}
}
return self;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建Garage对象
Garage *garage = [[Garage alloc] init];
garage.name = @"My Garage";
// 创建Car对象并添加到Garage
Car *car1 = [[Car alloc] init];
car1.name = @"Herbie";
car1.make = @"Honda";
car1.model = @"CRX";
car1.modelYear = 1984;
car1.numberOfDoors = 2;
car1.mileage = 110000;
[garage addCar:car1];
Car *car2 = [[Car alloc] init];
car2.name = @"Badger";
car2.make = @"Acura";
car2.model = @"Integra";
car2.modelYear = 1987;
car2.numberOfDoors = 5;
car2.mileage = 217036.7;
[garage addCar:car2];
// 打印Garage信息
[garage print];
// 保存到文件
NSString *filePath = @"/Users/yourusername/Documents/garage.data";
[garage saveToFile:filePath];
// 从文件读取
Garage *loadedGarage = [[Garage alloc] initWithFile:filePath];
[loadedGarage print];
// 使用键值编码进行数据操作
NSNumber *count = [loadedGarage valueForKeyPath: @"cars.@count"];
NSLog (@"We have %@ cars", count);
NSNumber *sum = [loadedGarage valueForKeyPath: @"cars.@sum.mileage"];
NSLog (@"We have a grand total of %@ miles", sum);
NSNumber *avgMileage = [loadedGarage valueForKeyPath: @"cars.@avg.mileage"];
NSLog (@"average is %.2f", [avgMileage floatValue]);
NSNumber *min = [loadedGarage valueForKeyPath: @"cars.@min.mileage"];
NSNumber *max = [loadedGarage valueForKeyPath: @"cars.@max.mileage"];
NSLog (@"minimax: %@ / %@", min, max);
}
return 0;
}
这个示例代码展示了如何创建
Car
和
Garage
对象,将
Garage
中的
Car
对象保存到文件,从文件中读取对象,以及使用键值编码进行数据统计。
graph LR
A[创建对象] --> B[文件保存]
B --> C[文件读取]
C --> D[键值编码操作]
D --> E[数据统计]
通过以上内容,我们全面了解了文件加载与保存以及键值编码的相关知识和应用,希望能帮助你在实际开发中更好地运用这些技术。
超级会员免费看
9682

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



