19.3 编码方法和解码方法

可以使用刚刚描述的方式归档和恢复NSString、NSArray、NSDictionary、NSSet、NSDate、NSNumber和NSData之类的基本Objective-C类对象。这还包括嵌套的对象,如包含字符串,甚至其他数组对象的数组。

这意味着不能直接使用这种方法归档AddressBook,因为Objective-C系统不知道如何归档AddressBook对象。如果在程序中插入如下一行来尝试归档它:


[NSKeyedArchiver archiveRootObject:myAddressBook toFile:@“addrbook.arch”];


运行该程序时将会得到以下所示的消息:


*-[AddressBook encodeWithCoder:]:selector not recognized

*Uncaught exception:<NSInvalidArgumentException>

*-[AddressBook encodeWithCoder:]:selector not recognized

archiveTest:received signal:Trace/BPT trap


从这些出错消息中,可以看到系统正在AddressBook类中查找一个名为encodeWithCoder:的方法,但你从未定义过这样的方法。

要归档前面没有列出的对象,必须告知系统如何归档(或编码)你的对象,以及如何解归档(或解码)它们。这是按照<NSCoding>协议,在类定义中添加encodeWithCoder:方法和initWithCoder:方法实现的。对于我们地址簿的例子,必须向AddressBook类和AddressCard类添加这些方法。

每次归档程序想要根据指定类编码对象时,都将调用encodeWithCoder:方法,该方法告知归档程序如何进行归档。类似地,每次从指定的类解码对象时,就会调用initWithCoder:方法。

一般而言,编码方法应该指定如何归档想要保存的对象中的每个实例变量。幸运的是,这些都有帮助可查。对于前面描述的基本Objective-C类,可以使用encodeObject:forKey:方法。相反,对于基本的C数据类型(如整型和浮点型),可以使用表19-1中列出的某种方法。解码方法initWithCoder:的工作方式正好相反:它使用decodeObject:forKey:来解码基本的Objective-C类,使用19-1中列出的相应解码方法来解码基本的数据类型。

19.3 编码方法和解码方法 - 图1

代码清单19-5为AddressCard类和AddressBook类都添加了两个编码和解码方法。

代码清单19-5 Addresscard.h接口文件


import<Foundation/NSObject.h>

import<Foundation/NSString.h>

import<Foundation/NSKeyedArchiver.h>

@interface AddressCard:NSObject<NSCoding, NSCopying>

{

NSString*name;

NSString*email;

}

@property(copy, nonatomic)NSStringname,email;

-(void)setName:(NSString)theName andEmail:(NSString)theEmail;

-(NSComparisonResult)compareNames:(id)element;

-(void)print;

//Additional methods for NSCopying protocol

-(AddressCard)copyWithZone:(NSZone)zone;

-(void)retainName:(NSString)theName andEmail:(NSString)theEmail;

@end


下面是要添加到AddressCard类实现文件的两个新方法:


-(void)encodeWithCoder:(NSCoder*)encoder

{

[encoder encodeObject:name forKey:@“AddressCardName”];

[encoder encodeObject:email forKey:@“AddressCardEmail”];

}

-(id)initWithCoder:(NSCoder*)decoder

{

name=[[decoder decodeObjectforKey:@“AddressCardName”]retain];

email=[[decoder decodeObjectforKey:@“AddressCardEmail”]retain];

return self;

}


该程序向编码方法encodeWithCoder:传入一个NSCoder对象作为参数。由于AddressCard类直接继承自Nsobject,所以无需担心编码继承的实例变量。如果的确担心,并且知道类的子类符合NSCoding协议的要求,那么应该用下面的语句开始编码方法,确保继承的实例变量也被编码:


[super encodeWithCoder:encoder];


对于地址簿来说,有两个名为name和email的实例变量。因为它们都是NSString对象,所以使用encodeObject:forKey:方法依次对它们进行编码,然后将这两个实例变量添加到归档文件中。

encodeObject:forKey:方法编码对象并将其存储在指定的键下,以后可使用该键检索对象。键名是任意的,所以只要在检索(编码)数据时使用的名称与归档(编码)时使用的名称相同,就可以指定任何键名。唯一可能出现冲突的情况是,为正在编码的对象子类使用了相同的键。为了防止这种情况出现,制订归档的键时,可将类名放在实例变量名的前面,代码清单19-5就是这样做的。

注意,encodeObject:ForKey:方法可以用于任何在其类中实现对应encodeWithCoder:方法的对象。

解码的过程刚好相反。传递给initWithCoder:的参数也是NSCoder对象。不必担心这个参数,只要记住它是获得该消息(对于每个想要从归档文件中提取的对象)的对象。

同样,由于AddressCard类直接继承自NSObject,所以不必担心解码继承的实例变量。如果的确担心,那么应在解码方法的开始插入下列行(假设类的超类符合NSCoding协议的要求):


self=[super initWithCoder:decoder];


通过调用decodeObject:ForKey:方法并传递在编码变量时使用的相同键,就可解码每个实例变量。

类似于AddressCard类,你为AddressBook类添加了两个编码和解码方法。在接口文件中只需更改@interface指令,以声明现在AddressBook类已经符合NSCoding协议。更改如下所示:


@interface AddressBook:NSObject<NSCoding, NSCopying>


下面是实现文件中所含的方法定义:


-(void)encodeWithCoder:(NSCoder*)encoder

{

[encoder encodeObject:bookName forKey:@“AddressBookBookName”];

[encoder encodeObject:book forKey:@“AddressBookBook”];

}

-(id)initWithCoder:(NSCoder*)decoder

{

bookName=[[decoder decodeObjectForKey:@“AddressBookBookName”]retain];

book=[[decoder decodeObjectForKey:@“AddressBookBook”]retain];

return self;

}


以下代码清单19-6是它的测试程序。

代码清单19-6测试程序


import“AddressBook.h”

import<Foundation/NSAuoreleaePool.h>

int main(int argc, char*argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

NSString*aName=@“Julia Kochan”;

NSString*aEmail=@“jewls337@axlc.com”;

NSString*bName=@“Tony Iannino”;

NSString*bEmail=@“tony.iannino@techfitness.com”;

NSString*cName=@“Stephen Kochan”;

NSString*cEmail=@“steve@steve_kochan.com”;

NSString*dName=@“Jamie Baker”;

NSString*dEmail=@“jbaker@hitmail.com”;

AddressCard*card1=[[AddressCard alloc]init];

AddressCard*card2=[[AddressCard alloc]init];

AddressCard*card3=[[AddressCard alloc]init];

AddressCard*card4=[[AddressCard alloc]init];

AddressBook*myBook=[AddressBook alloc];

//First set up four address cards

[card1 setName:aName andEmail:aEmail];

[card2 setName:bName andEmail:bEmail];

[card3 setName:cName andEmail:cEmail];

[card4 setName:dName andEmail:dEmail];

myBook=[myBook initWithName:@“Steves Address Book”];

//Add some cards to the address book

[myBook addCard:card1];

[myBook addCard:card2];

[myBook addCard:card3];

[myBook addCard:card4];

[myBook sort];

if([NSKeyedArchiver archiveRootObject:myBook toFile:@“addrbook.arch”]==NO)

NSLog(@“archiving failed”);

[card1 release];

[card2 release];

[card3 release];

[card4 release];

[myBook release];

[pool drain];

return 0;

}


这个程序创建了一个地址簿,然后将它归档到文件addrbook.arch中。在创建归档文件的过程中,注意AddressBook类和AddressCard类中的编码方法都被调用了。如果想要验证,可以向这些方法添加一些NSLog调用。

代码清单19-7展示了如何将归档读入内存以根据文件创建地址簿。

代码清单19-7


import“AddressBook.h”

import<Foundation/NSAutoreleasePool.h>

int main(int argc, char*argv[])

{

AddressBook*myBook;

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

myBook=[NSKeyedUnarchiver unarchiveObjectWithFile:@“addrbook.arch”];

[myBook list];

[pool drain];

return 0;

}


代码清单19-7输出


========Contents of:Steves Address Book=========

Jamie Baker jbaker@hitmail.com

Julia Kochan jewls337@axlc.com

Stephen Kochan steve@steve_kochan.com

Tony Iannino tony.iannino@techfitness.com

====================================================


在解码地址簿的过程中,自动调用向两个类添加的解码方法。注意将地址薄读回程序是多么容易。

前面说过,encodeObject:forKey:方法作用于内置类以及根据NSCoding协议为其编写编码和解码方法的类。如果你的实例包含基本数据类型,如整型或浮点型,那么需要知道如何对它们进行编码和解码(参见表19-1)。

下面是一个类的简单定义,这个类名为Foo,它包含三个实例变量:一个是NSString类型,一个int型,一个float型。这个类包含一个赋值方法、三个取值方法以及两个用于归档的编码/解码方法;


@interface Foo:NSObject<NSCoding>

{

NSString*strVal;

int intVal;

float floatVal;

}

@property(copy, nonatomic)NSString*strVal;

@property int intVal;

@property float floatVal;

@end


实现文件如下:


@implementation Foo

@synthesize strVal, intVal, floatVal;

-(void)encodeWithCoder:(NSCoder*)encoder

{

[encoder encodeObject:strVal forKey:@“FoostrVal”];

[encoder encodeInt:intVal forKey:@“FoointVal”];

[encoder encodeFloat:floatVal forKey:@“FoofloatVal”];

}

-(id)initWithCoder:(NSCoder*)decoder

{

strVal=[[decoder decodeObjectForKey:@“FoostrVal”]retain];

intVal=[decoder decodeIntForKey:@“FoointVal”];

floatVal=[decoder decodeFloatForKey:@“FoofloatVal”];

return self;

}

@end


编码例程首先使用前面用过的encodeObject:forKey:对象来编码字符串值strVal,如上面内容所示。

在代码清单19-8中,我们创建了一个Foo对象,把它归档到一个文件,解归档,然后显示。代码清单19-8测试程序


import<Foundation/NSObject.h>

import<Foundation/NSString.h>

import<Foundation/NSKeyedArchiver.h>

import<Foundation/NSAutoreleasePool.h>

import“Foo.h”//Definition for our Foo class

int main(int argc, char*argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];

Foo*myFoo1=[[Foo alloc]init];

Foo*myFoo2;

[myFoo1 setStrVal:@“This is the string”];

[myFoo1 setIntVal:12345];

[myFoo1 setFloatVal:98.6];

[NSKeyedArchiver archiveRootObject:myFoo1 toFile:@“foo.arch”];

myFoo2=[NSKeyedUnarchiver unarchiveObjectWithFile:@“foo.arch”];

NSLog(@“@\n%i\n%g”,[myFoo2 strVal],[myFoo2 intVal],[myFoo2 floatVal]);

[myFoo1 release];

[pool drain];

return 0;

}


代码清单19-8输出


This is the string

12345

98.6


以下消息归档了对象的3个实例变量:


[encoder encodeObject:strVal forKey:@“FoostrVal”];

[encoder encodeInt:intVal forKey:@“FoointVal”];

[encoder encodeFloat:floatVal forKey:@“FoofloatVal”];


一些基本数据类型,如char、short、long和long long在表19-1中没有列出。你必须确定数据对象的大小并使用相应的例程。例如,short int通常是16位的,而int和long可以是32位或64位,long long是64位的(可以使用第13章介绍的sizeof运算符确定任何数据类型的大小)。所以要归档short int的数据,首先将其存储在int中,然后使用encodeInt:forKey:归档它。反向执行该过程可恢复它:使用decodeIntForKey:,然后将其赋值给short int变量。