- 19.3 编码方法和解码方法
- import<Foundation/NSObject.h>
- import<Foundation/NSString.h>
- import<Foundation/NSKeyedArchiver.h>
- import“AddressBook.h”
- import<Foundation/NSAuoreleaePool.h>
- import“AddressBook.h”
- import<Foundation/NSAutoreleasePool.h>
- import<Foundation/NSObject.h>
- import<Foundation/NSString.h>
- import<Foundation/NSKeyedArchiver.h>
- import<Foundation/NSAutoreleasePool.h>
- import“Foo.h”//Definition for our Foo class
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-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变量。