30、文件加载与保存及键值编码技术解析

文件加载与保存及键值编码技术解析

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[结束]

通过对键值编码的深入学习和实践,我们可以更好地利用这些技术来解决实际开发中的问题,提高开发效率和代码质量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值