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

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

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[数据统计]

通过以上内容,我们全面了解了文件加载与保存以及键值编码的相关知识和应用,希望能帮助你在实际开发中更好地运用这些技术。

内容概要:本文详细介绍了“秒杀商城”微服务架构的设计实战过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值