17 文件的加载与保存

17.1 属性列表

17.1.1 NSDate

date类方法

说明:获取当前日期和时间。
原型:NSDate

1
2
3
4
/**
* @return {instancetype} NSDate类实例
*/

+ (instancetype)date;

dateWithTimeIntervalSinceNow类方法

说明:获取与当前时间相隔一定时差的日期
原型:NSDate

1
2
3
4
/**
*@return {instancetype} NSDate实例
*/

+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
1
2
3
4
5
6
7
// 获取当前的日期和时间
NSDate *date = [NSDate date];
NSLog(@"today is %@", date);

// 获取24小时之前的时间
NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:-(24* 60 *60)];
NSLog(@"yesterday is %@", yesterday);

17.1.2 NSData

说明:该类可以包含大量的字节,可以获得数据的长度和指向字节启示位置的指针
用途:如果想将数据块传递给一个函数或方法,可以通过传递一个NSData来实现。
注意:NSData是一个对象,支持自动释放的,常规的内存管理对它是有效的,因而无需担心内存清理的问题。

dataWithBytes类方法

说明:创建一个保存一个普通C字符串(一个字节序列)的NSData对象。
原型:NSData

1
2
3
4
5
6
/**
* @param {nullable const void *} bytes 字符串
* @param {NSUInteger} length 字符串的长度(包括尾部的`\0`)
* @return {instancetype} NSData实例
*/

+ (instancetype)dataWithBytes:(nullable const void *)bytes length:(NSUInteger)length;

扩展:NSData是不可变的,创建后不能改变其中的内容来;NSMutableData是可变的,可以在数据中添加和删除字节。

1
2
3
4
5
6
7
// c 语言字符串
const char *string = "hi there, this is a C string!";
// 创建 NSData 对象
NSData *data = [NSData dataWithBytes:string length:strlen(string) + 1];
// 操作 NSData 对象
NSLog(@"data is %@", data);
NSLog(@"%ld byte string is '%s'", [data length], [data bytes]);

17.1.3 写入和读取属性列表

说明:属性列表类可以存储到文件中,也可以从文件中读取出来。此外,Xcode包含一个属性列表编辑器,可以用来方便地查看plist文件。
注意:如果出现问题,下面介绍的函数都不会返回具体的错误的原因。

writeToFile实例方法

说明:将属性列表的内容写入到文件。
技巧:应尽量使用atomically的方式保存文件,除非保存的文件容量非常大,会占用用户大量的磁盘空间。
原型:NSArray

1
2
3
4
5
/**
* @param {NSString *} 文件路径
* @param {BOOL}useAuxiliaryFile 是否首先将文件内容保存到临时文件中(防止极端情况下原始文件被破坏)
*/

(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;

arrayWithContentsOfFile类方法

说明:读取文件中的数组信息并据此创建数组。
原型:NSArray

1
2
3
4
5
/**
* @param {NSString *} path 文件路径
* @return {NSArray} 数组实例
*/

+ (nullable NSArray<ObjectType> *)arrayWithContentsOfFile:(NSString *)path;
1
2
3
4
5
6
7
8
// 创建 NSArray 实例
NSArray *phrase;
phrase = [NSArray arrayWithObjects:@"I", @"seem", @"to", @"be", @"a", @"verb", nil];
// 将 NSArray 对象写入文件
[phrase writeToFile:@"/tmp/verbiage.txt" atomically:YES];
// 通过文件创建 NSArray 实例
NSArray *phrase2 = [NSArray arrayWithContentsOfFile:@"/tmp/veribage.txt"];
NSLog(@"%@", phrase2);

/tmp/verbiage.txt

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>I</string>
<string>seem</string>
<string>to</string>
<string>be</string>
<string>a</string>
<string>verb</string>
</array>
</plist>

17.1.4 修改对象类型

说明:存储或加载属性列表时可以使用NSPropertyListSerialization添加一些设定项。

dataFromPropertyList类方法

说明:存储到文件时指定设定项。已过时。
原型:NSPropertyListSerialization

1
2
3
4
5
6
/**
* @param {id} plist 属性列表类型对象
* @param {NSPropertyListFormat} format 存储属性列表的方式
* @param {NSString *} errorDescription 错误信息
*/

+ (nullable NSData *)dataFromPropertyList:(id)plist format:(NSPropertyListFormat)format errorDescription:(out __strong NSString * __nullable * __nullable)errorString NS_DEPRECATED(10_0, 10_10, 2_0, 8_0, "Use dataWithPropertyList:format:options:error: instead.");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 可变数组(首都集合)
NSMutableArray *capitols = [NSMutableArray arrayWithCapacity:10];
// 第一个国家
NSMutableDictionary *capitol = [NSMutableDictionary dictionaryWithObject:@"Canada" forKey:@"country"];
[capitol setObject:@"Ottawa" forKey:@"capitol"];
[capitols addObject:capitol];
// 第二个国家
capitol = [NSMutableDictionary dictionaryWithObject:@"Norway" forKey:@"country"];
[capitol setObject:@"Oslo" forKey:@"capitol"];
[capitols addObject:capitol];

NSString *error = nil;
// 将 plist 数据内容以二进制形式写入文件
NSData *encodeArray = [NSPropertyListSerialization dataFromPropertyList:capitols format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[encodeArray writeToFile:@"/tmp/capitols.txt" atomically:YES];

propertyListFromData实例方法

说明:以指定形式将文件读取到内存。
原型:NSPropertyListSerialization

1
2
3
4
5
6
/**
* @param {NSData *} data 从文件读取原始数据
* @param {NSPropertyListMutabilityOptions} opt format 读取形式
* @param {NSString *} errorString 可能的错误信息
*/

+ (nullable id)propertyListFromData:(NSData *)data mutabilityOption:(NSPropertyListMutabilityOptions)opt format:(nullable NSPropertyListFormat *)format errorDescription:(out __strong NSString * __nullable * __nullable)errorString NS_DEPRECATED(10_0, 10_10, 2_0, 8_0, "Use propertyListWithData:options:format:error: instead.");
1
2
3
4
5
6
7
8
// 从文件中读取属性列表数据
NSData *data = [NSData dataWithContentsOfFile:@"/tmp/capitols.txt"];

// 以指定的格式将文件中的内容读取到内存
NSPropertyListFormat properyListFormat = NSPropertyListXMLFormat_v1_0;
NSString *error = nil;
NSMutableArray *capitols = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&properyListFormat errorDescription:&error];
NSLog(@"capitols %@", capitols);

17.2 编码对象

说明:Cocoa具备一种将任意对象转换成某种格式并保存到磁盘中的机制。

  • 序列化(编码):将对象的实例变量和其他数据编码为数据块,然后保存到磁盘
  • 反序列化(解码):将数据块读回内存,并基于保存的数据创建新对象

NSCoding协议

说明:通过采纳该协议,可以使对象具备序列化和反序列化的能力。
原型:

1
2
3
4
5
6
@protocol NSCoding
// 序列化
- (void) encodeWithCoder: (NSCoder *) encoder;
// 反序列化
- (id) initWithCoder: (NSCoder *) decoder;
>@end

archiveDataWithRootObject类方法

说明:对对象进行编码。

  1. 创建了一个KSKeyedArchier实例
  2. 将上一步创建的实例传递给参数指定的对象的encodeWithCoder方法
  3. 递归编码自身使用到的其它对象,比如字符串、数组及放入数组中的任何对象
  4. 所有对象完成键值编码后,被放入一个NSData对象并返回

原型:KSKeyedArchiver

1
2
3
4
5
/**
* @param {id} rootObject 要被序列化的对象
* @return {NSData *} 序列化为 NSData 型数据
*/

+ (NSData *)archivedDataWithRootObject:(id)rootObject;

Thingie.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import <Foundation/Foundation.h>

@interface Thingie : NSObject
{
NSString *name;
int magicNumber;
float shoeSize;
NSMutableArray *subThingies;
}
@property (copy) NSString *name;
@property int magicNumber;
@property float shoeSize;
@property (retain) NSMutableArray *subThingies;

- (id) initWithName: (NSString *) n
magicNumber: (int) mn
shoeSize: (float) ss;

@end

Thingie.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#import "Thingie.h"

@implementation Thingie

@synthesize name;
@synthesize magicNumber;
@synthesize shoeSize;
@synthesize subThingies;

// 便利构造函数:通过用户名
- (id) initWithName:(NSString *)
magicNumber:(int)mn
shoeSize:(float)ss
{
if (self = [super init])
{
self.name = n;
self.magicNumber = mn;
self.showSize = ss;
self.subThingies = [NSMutableArray array];
}
return (self);
}

// 采纳协议:反序列化
- (id) initWithCoder: (NSCoder *) decoder
{
if (self = [super init])
{
self.name = [decoder decodeObjectForKey:@"name"];
self.magicNumber = [decoder decodeIntForKey:@"magicNumber"];
self.shoeSize = [decoder decodeIntForKey:@"shoeSize"];
self.subThingies = [decoder decodeObjectForKey:@"subThingies"];
}
return (self);
}

// 采纳协议:序列化
- (void) encodeWithCoder: (NSCoder *) coder
{
[coder encodeObject:name
forKey:@"name"];
[coder encodeInt:magicNumber
forKey:@"magicNumber"];
[coder encodeFloat:shoeSize
forKey:@"shoeSize"];
[coder encodeObject:subThingies
forKey:@"subThingies"];
}

- (NSString *) description{
NSString *description = [NSString stringWithFormat:@"%@: %d/%.1f %@", name, magicNumber, shoeSize, subThingies];
return (description);
}

- (void) dealloc
{
[name release];
[subThingies release];
[super dealloc];
}

@end

main.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#import <Foundation/Foundation.h>
#import "Thingie.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
/** 简单情形 **/
// 创建可序列化的对象
Thingie *thing1;
thing1 = [[Thingie alloc]
initWithName:@"thing1"
magicNumber:42
shoeSize:10.5];
NSLog(@"some thing: @", thing1);
// 序列化为 NSData 对象
NSData *freezeDried;
freezeDried = [NSKeyedArchiver archivedDataWithRootObject:thing1];
[thing1 release];

// 反序列化
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData:freezeDried];
NSLog(@"reconstitued thing: %@", thing1);

/* 复杂情形:递归序列化 */
// 初始化thing1.subThingies(数组)
Thingie *anotherThing;
anotherThing = [[[Thingle alloc]
initWithName:@"thing2"
magicNumber:23 shoeSize:13.0]];
[thing1.subThingies addObject:anotherThing];
anotherThing = [[[Thingle alloc]
initWithName:@"thing3"
magicNumber:17 shoeSize:9.0]];
[thing1.subThingies addObject:anotherThing];
NSLog(@"reconstitued muthing: %@", thing1);
// 序列化
[freezeDried = [NSKeyedArchiver archivedDataWithRootObject:thing1];
// 反序列化
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData:freezeDried];
NSLog(@"reconstitued multiting: %@", thing1);

/* 复杂情形:设置一处引用自己的数据,序列化和反序列化仍能正常工作 */
[thing1.subThingies addObject:thing1];
// 序列化
freezeDried = [NSKeyedArchiver archivedDataWithRootObject:thing1];
// 反序列化
thing1 = [NSKeyedUnarchiver unarchiveObjectWithData:freezeDried];
}
return 0;
}

17.3 小结