http://www.ituring.com.cn/article/214104
在太空军事学院里,还有一个军事生化技术研究中心,这一天,突然来了几艘医疗太空船,据了解,这些太空船带来很多受来外星生化袭击的太空舰队人员;而他们来到这里的目的很简单,通过医疗和生物改造而活下去。而这些工作则是由A博士主持的,他的军衔为中校。
A博士:这些受伤的人都是通过冷冻后运送过来,否则他们将不会存活;在了解更多的技术之前,我们先了解如何进人员的冷冻和解冻,前提是,要活的!
在Objective-C项目中,我们可以将对象归档至文件,然后可以通过解档操作快速恢复它们;此外,我们还可以利用归档快速完全复制(深复制)对象。本章,我们就来讨论这些内容。
9.1. 归档与解档
A博士:我们的冷冻技术主要使用了NSKeyedArchiver类和NSKeyedUnarchiver类,它们分别用于归档和解档,而档案的形式可以是NSData对象、NSMutableData对象或文件。只是有一点需要注意,归档和解档操作的类必须实现了NSCoding协议。
9.1.1. 实现NSCoding协议
A博士,接下来,我们会对受到外星生化攻击的士兵进行改造,创建CBionicle类型生化战士;要知道,让他完全恢复是不可能的,所以,这也是唯一让他们活下去的办法了。在这里,CBionicle类会实现NSCoding协议,下面就是接口部分(CBionicle.h文件)。
#ifndef __CBionicle_h__
#define __CBionicle_h__
#import <Foundation/Foundation.h>
@interface CBionicle : NSObject <NSCoding>
@property NSString *name;
@property NSPoint position;
@property int life;
-(void) fire;
@end
#endif
接下来是CBionicle类的实现部分。请注意,在实现部分,除了接口中定义的成员,我们还必须实现initWithCoder:方法和encodeWithCoder:方法,它们都是在NSCoding协议中定义的。如下面的代码(CBionicle.m文件)。
#import "CBionicle.h"
@implementation CBionicle
@synthesize name, position, life;
-(void) fire
{
NSLog(@"%@ 正在开火!", self.name);
}
// NSCoding协议方法
//
-(void) encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:@"CBionicle_name"];
[aCoder encodePoint:self.position forKey:@"CBionicle_position"];
[aCoder encodeInt:self.life forKey:@"CBionicle_life"];
}
//
-(id) initWithCoder:(NSCoder *)aDecoder
{
self.name = [aDecoder decodeObjectForKey:@"CBionicle_name"];
self.position = [aDecoder decodePointForKey:@"CBionicle_position"];
self.life = [aDecoder decodeIntForKey:@"CBionicle_life"];
return self;
}
@end
A博士:我们可以看到NSCoder类中定义的按键(Key)编码和解码的方法,这些方法可以帮助我们对各种类型的数据进行编码和解码,以便进行归档和解档操作。常用的方法包括:
- encodeObject:forKey:方法和decodeObject:forKey:方法,用于对各种对象进行编码和解码操作。
- encodeBool:forKey:方法和decodeBool:forKey:方法,对BOOL类型数据进行编码和解码操作。
- encodeInt:forKey:方法和decodeInt:forKey:方法,对int类型数据进行编码和解码操作。
- encodeInteger:forKey:方法和decodeInteger:forKey:方法,对NSInteger类型数据进行编码和解码操作。
- encodeFloat:forKey:方法和decodeFloat:forKey:方法,对float类型数据进行编码和解码操作。
- encodeDouble:forKey:方法和decodeDouble:forKey:方法,对double类型数据进行编码和解码操作。
- encodePoint:forKey:方法和decodePoint:forKey:方法,对NSPoint结构数据进行编码和解码操作。
- encodeSize:forKey:方法和decodeSize:forKey:方法,对NSSize结构数据进行编码和解码操作。
- encodeRect:forKey:方法和decodeRect:forKey:方法,对NSRect结构数据进行编码和解码操作。
9.1.2. 使用NSKeyedArchiver类和NSKeyedUnarchiver类
A博士:我们创建的CBionicle类已经实现了NSCoding协议,接下来,我们就可以对这个类型的对象,也就是具体的生化士兵进行冷冻(归档)和解冻(解档)操作了。首先,我们看一看如何进行归档操作,如下面的代码。
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = paths.firstObject;
// 归档文件
NSString *filename =
[docPath stringByAppendingPathComponent:@"SoldierSmith"];
//
CBionicle *soldier = [[CBionicle alloc] init];
soldier.position = CGMakePoint(10, 15);
soldier.name = @"Smith";
NSLog(@"%i", [NSKeyedArchiver archiveRootObject:soldier toFile:filename]);
我们可以在main()函数中测试这些代码,请注意,不要忘记使用#import指令引用"CBionicle.h"文件。代码成功执行后,会在“文稿”目录中生成一个名为SoldierSmith的文件。
A博士:现在,Smith已经冷冻到“文稿”目录了,下面的代码,我们将利用NSKeyedUnarchiver类对其进行解冻(解档)操作。
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = paths.firstObject;
// 归档文件
NSString *filename =
[docPath stringByAppendingPathComponent:@"SoldierSmith"];
//
CBionicle *soldier = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
NSLog(@"%@", soldier.name);
[soldier fire];
A博士:通过实现NSCoding协议,以及归档操作,我们可以发现自己定义的类也可以很方便地进行持久化,如将对象数据保存到磁盘文件。然后,我们可通过解档操作方便、快捷地恢复对象。
在Foundation框架和其它开发资源中,我们同样可以通过归档和解档对已经实现了NSCoding协议的对象进行持久化和恢复操作。此外,有些类型已经定义了将对象数据写入文件和读取的方法,我们可以很方便地对它们进行归档和解档操作,如NSArray等。
9.2. 克隆生化战士(利用归档复制对象)
A博士:在实际应用中,虽然生化士兵损失不小,但其战斗力还是很强大的,所以,在军事生化技术研究中心,我们又想到了一个办法创造更多的生化战士,就是利用快速克隆技术进行克隆。这就是归档的另一个功能,即快速地复制一个对象,请注意,这里是指对象的深复制操作。
如果类型实现了NSCopying协议,则可以直接使用clone方法进行深复制操作;不过,如果类型没有实现NSCopying协议,但不小心实现了NSCoding协议,那么,我们就可以使用归档的方法进行这类对象的深复制操作,此时,我们需要NSData对象作用“中间人”。
如下面的代码,我们将对士兵Smith进行克隆(深复制),请注意,CBionicle类并没有实现NSCopying协议。
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = paths.firstObject;
// 归档文件
NSString *filename =
[docPath stringByAppendingPathComponent:@"SoldierSmith"];
//
CBionicle *soldier = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:soldier];
// 开始克隆10个Smith
CBionicle *soldiers[10];
for (int i=0; i < 10 ; i++)
{
soldiers[i] = [NSKeyedUnarchiver unarchiveObjectWithData:data];
soldiers[i].name = [NSString stringWithFormat:@"Smith-%i", i+1];
}
// 全体开火
for (int i=0; i < 10 ; i++)
{
[soldiers[i] fire];
}
9.3. 游戏存档
A博士:我们还没开始开发游戏呢,怎么就开始存档了?要知道,已经越来越接近太空舰队远征的日期了,而在旅行或挑战中能够时刻保存一些数据当然是有必要的,我想,L上尉也会有这样的想法,这样,她的图书馆又会有更多的资源了。
实际上,当我们开始讨论游戏的开发以后,大家就会发现,在游戏中需要保存的数据类型及保存方法实际上已经讨论过了;在游戏中,我们使用的数据类型更多的会是int、NSInteger、float、CGFloat、CGPoint、CGSize等一些常用的数据或结构,而保存这些数据时,我们可以使用NSMutableArray对象组织这些数据,然后,通过writeToFile::方法直接保存到存档文件;当然,这个存档文件可能在应用的“沙盒(sandbox)”里,也可能在用户的文稿目录中。
这只是一个基本的保存游戏数据的方法,在实际的项目中,我们需要根据数据量的大小,以及格式要求灵活地使用保存策略,甚至,对于大量的数据,我们还可以使用数据库,如CoreData、SQLite等。
A博士:听说大家就要启航了,祝各位在太空里好运!