30、文件加载与保存及键值编码详解

文件加载与保存及键值编码详解

1. 文件加载与保存

在数据处理过程中,编码和解码操作至关重要。以下代码展示了基本的编码和解码过程:

freezeDried = [NSKeyedArchiver archivedDataWithRootObject: thing1];
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData: freezeDried];
NSLog (@"reconstituted multithing: %@", thing1);

当被编码的数据存在循环引用时,例如 thing1 存在于其自身的 subThingies 数组中, NSKeyedArchiver NSKeyedUnarchiver 能够巧妙地处理这种情况,避免无限递归。不过,若使用 NSLog 打印包含循环引用的对象,会导致无限递归,最终进入调试器。

在文件加载和保存方面,提供了两种技术:属性列表(plists)和对象编码。属性列表数据类型的对象可以方便地保存到磁盘并读取回来。对于非属性列表类型的自定义对象,可以采用 NSCoding 协议,实现编码和解码方法,将对象转换为 NSData 进行保存和恢复。

2. 键值编码(KVC)概述

键值编码(Key-Value Coding,简称 KVC)是一种间接改变对象状态的机制,通过字符串描述要改变的对象属性。一些高级的 Cocoa 特性,如 Core Data 和 Cocoa Bindings,都将 KVC 作为基础机制。

3. 入门项目

Car 类为例,添加了一些属性,如 make model 等,并使用 @synthesize 自动生成访问器方法。以下是 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;
// 其他方法...
@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;
        // 设置轮胎和引擎...
        NSLog (@"Car is %@", car);
    }
    return (0);
}
4. KVC 基本调用

KVC 的基本调用方法是 -valueForKey: -setValue:forKey: 。例如,获取汽车的名称和品牌:

NSString *name = [car valueForKey:@"name"];
NSLog (@"%@", name);
NSLog (@"make is %@", [car valueForKey:@"make"]);

-valueForKey: 首先查找以键命名的 getter 方法,若不存在则查找实例变量。对于标量值,KVC 会自动进行装箱和拆箱操作。设置属性值时,使用 -setValue:forKey: ,对于标量值需要先进行装箱:

[car setValue: @"Harold" forKey: @"name"];
[car setValue: [NSNumber numberWithFloat: 25062.4] forKey: @"mileage"];
5. 键路径(Key Path)

键值编码允许指定键路径,类似于文件系统路径,可用于访问对象的嵌套属性。例如,为 Engine 类添加 horsepower 属性:

@interface Engine : NSObject <NSCopying>
{
    int horsepower;
}
@end

- (id) init
{
    if (self = [super init])
    {
        horsepower = 145;
    }
    return (self);
}

通过键路径访问和设置 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"]);
6. 聚合操作

KVC 还支持对数组进行聚合操作。例如,获取汽车轮胎的气压:

NSArray *pressures = [car valueForKeyPath: @"tires.pressure"];
NSLog (@"pressures %@", pressures);

valueForKeyPath: 会将键路径拆分为多个部分,依次处理。

7. 车库类(Garage)

为了进一步演示 KVC 的功能,引入了 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];
        // 添加更多汽车...
        [garage print];
        [garage release];
    }
    return (0);
}
8. 键路径操作符

键路径还支持一些操作符,如 @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[选择操作类型]
    B --> C{获取值}
    B --> D{设置值}
    C --> E[使用 -valueForKey: 或 -valueForKeyPath:]
    E --> F{是否找到 getter 方法}
    F -->|是| G[返回值]
    F -->|否| H{是否找到实例变量}
    H -->|是| G
    H -->|否| I[返回 nil]
    D --> J[使用 -setValue:forKey: 或 -setValue:forKeyPath:]
    J --> K{是否找到 setter 方法}
    K -->|是| L[设置值]
    K -->|否| M{是否找到实例变量}
    M -->|是| L
    M -->|否| N[不做处理]
    G --> O[结束]
    I --> O
    L --> O
    N --> O

通过以上内容,我们详细介绍了文件加载与保存以及键值编码的相关知识和操作方法,希望能帮助你更好地理解和应用这些技术。

文件加载与保存及键值编码详解

9. 键值编码的优势与应用场景

键值编码在实际开发中具有诸多优势,以下是一些常见的应用场景及优势分析:

  • 动态访问属性 :当需要在运行时动态访问对象的属性时,KVC 提供了极大的便利。例如,在开发一个通用的数据展示界面时,可能需要根据用户的选择动态显示对象的不同属性。使用 KVC 可以通过用户输入的属性名作为键,轻松获取相应的属性值。
// 假设 userInput 是用户输入的属性名
NSString *userInput = @"modelYear";
id value = [car valueForKey:userInput];
NSLog(@"The %@ of the car is %@", userInput, value);
  • 简化代码逻辑 :在处理复杂的对象关系时,使用 KVC 可以避免编写大量的嵌套方法调用。例如,通过键路径可以直接访问对象的嵌套属性,使代码更加简洁易读。
// 直接通过键路径访问汽车引擎的马力
NSNumber *horsepower = [car valueForKeyPath:@"engine.horsepower"];
NSLog(@"The horsepower of the car's engine is %@", horsepower);
  • 数据绑定与 UI 交互 :在一些需要将数据模型与 UI 控件进行绑定的场景中,KVC 可以实现数据的自动更新。例如,当用户在 UI 上修改了某个属性的值时,可以通过 KVC 将新值设置到对应的对象属性中。
// 假设 textField 是一个 UITextField,用于输入汽车的名称
[car setValue:textField.text forKey:@"name"];
10. 注意事项与潜在问题

在使用键值编码时,需要注意以下几点:

  • 属性名的正确性 :使用 KVC 时,键的名称必须与对象的属性名或实例变量名完全匹配。如果键名错误,可能会导致运行时错误或返回 nil
// 错误的键名,可能会导致返回 nil
id wrongValue = [car valueForKey:@"wrongKey"];
  • 内存管理 :虽然 KVC 本身不会影响对象的内存管理,但在使用 setValue:forKey: setValue:forKeyPath: 时,需要确保传入的值的内存管理正确。例如,对于需要保留的对象,需要进行适当的内存管理操作。
// 确保传入的对象已经进行了正确的内存管理
NSString *newName = [[NSString alloc] initWithString:@"NewName"];
[car setValue:newName forKey:@"name"];
[newName release];
  • 性能问题 :在某些情况下,频繁使用 KVC 可能会导致性能下降。因为 KVC 需要在运行时进行方法查找和实例变量访问,相比直接访问属性或调用方法,会有一定的性能开销。因此,在对性能要求较高的场景中,应谨慎使用 KVC。
11. 总结与展望

键值编码是一种强大而灵活的机制,它为开发者提供了一种间接访问和修改对象属性的方式。通过使用 KVC,可以实现动态访问属性、简化代码逻辑、数据绑定等功能。同时,在使用 KVC 时,需要注意属性名的正确性、内存管理和性能问题。

在未来的开发中,随着应用程序的复杂性不断增加,键值编码的应用场景也将更加广泛。例如,在大数据处理、人工智能等领域,KVC 可以用于动态配置和管理对象的属性,提高系统的灵活性和可扩展性。

常见问题解答
问题 解答
KVC 与直接访问属性有什么区别? 直接访问属性是通过点语法或方法调用直接访问对象的属性,而 KVC 是通过字符串键间接访问属性。KVC 提供了更大的灵活性,但可能会有一定的性能开销。
如何处理 KVC 中键名错误的情况? 可以在代码中进行错误处理,例如在获取值时检查返回值是否为 nil ,或者在设置值时捕获可能的异常。
KVC 支持哪些数据类型? KVC 支持大多数常见的数据类型,包括对象类型和标量类型。对于标量类型,KVC 会自动进行装箱和拆箱操作。
键值编码操作总结流程图
graph LR
    A[开始使用 KVC] --> B{选择操作}
    B --> C[获取属性值]
    B --> D[设置属性值]
    C --> E[确定键或键路径]
    E --> F[调用 -valueForKey: 或 -valueForKeyPath:]
    F --> G{是否找到对应值}
    G -->|是| H[使用值进行后续操作]
    G -->|否| I[处理错误或返回默认值]
    D --> J[确定键或键路径及新值]
    J --> K[调用 -setValue:forKey: 或 -setValue:forKeyPath:]
    K --> L{是否成功设置}
    L -->|是| M[完成设置]
    L -->|否| N[处理错误]
    H --> O[结束操作]
    I --> O
    M --> O
    N --> O

通过以上对键值编码的深入探讨,我们可以更好地掌握这一技术,并在实际开发中灵活运用,提高开发效率和代码质量。希望这些内容能帮助你在编程的道路上更进一步。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值