[ Team LiB ] Previous Section Next Section

Keyed Archives[2]

[2] Note that as of this writing that keyed archiving is not supported under GNUStep.

We noted in the discussion of the decoder methods that the fields in an archive must be read back in precisely the same order in which they were written. This technique might suit you; however, if you are creating archives from programs that might be going through many revisions, you might reorder some of the instance variables in one of your class definitions, or perhaps even add or remove some. In that case, restoring a previously created archive would be next to impossible.

A keyed archive is one in which each field of the archive has a name. When you archive an object, you give it a name, or key. When you retrieve it from the archive, you retrieve it by the same key. In that manner, objects can be written to the archive and retrieved in any order. Further, if new instance variables are added or removed to a class, the decoding method can account for it—for example, by setting a default value to a key that does not exist in the archive (for instance, if the archive were created by a different version of the program).

Instead of importing the file <Foundation/NSArchiver.h> in your interface file, to work with keyed archives you need to import <Foundation/NSKeyedArchiver.h>.

Referring to the Foo class defined in the previous example, if you define a class that others will use, you don't really know whether they'll try to archive objects from your class using keyed archiving. To account for that, you can write your encoding and decoding methods to handle either keyed or unkeyed archives. This can be done by sending an allowsKeyedCoding message to the encoder sent to your encodeWithCoder: method. If the answer is YES, you should use keyed archiving; otherwise, archive your objects in the manner described in the previous section. The same thing applies to your decoder: First, test whether keyed archiving is in effect and if it is, decode your instance variables accordingly.

Program 19.9 shows the modified Foo class interface and implementation files to allow for keyed archiving.

Program 19.9 Foo Interface File
@interface Foo: NSObject <NSCoding>
{
  NSString *strVal;
  int    intVal;
  float  floatVal;
}

-(void) setAll: (NSString *) ss iVal: (int) ii fVal: (float) ff;

-(NSString *) strVal;
-(int) intVal;
-(float) floatVal;
@end

The interface file hasn't changed from the previous example, but the implementation file has.

Program 19.9 Foo Implementation File
@implementation Foo;
-(void) setAll: (NSString *) ss iVal: (int) ii fVal: (float) ff
{
  strVal = ss;
  intVal = ii;
  floatVal = ff;
}

-(NSString *) strVal  { return strVal; }
-(int)        intVal  { return intVal; }
-(float)      floatVal { return floatVal; }


-(void) encodeWithCoder: (NSCoder *) encoder
{
    if ( [encoder allowsKeyedCoding] ) {
     [encoder encodeObject: strVal forKey: @"FoostrVal"];
     [encoder encodeInt: intVal forKey: @"FoointVal"];
     [encoder encodeFloat: floatVal forKey: @"FoofloatVal"];
    } else {
     [encoder encodeObject: strVal];
     [encoder encodeValueOfObjCType: @encode(int) at: &intVal];
     [encoder encodeValueOfObjCType: @encode(float) at: &floatVal];
    }
}

-(id) initWithCoder: (NSCoder *) decoder
{
    if ( [decoder allowsKeyedCoding] ) {
     strVal = [[decoder decodeObjectForKey: @"FoostrVal"] retain];
     intVal = [decoder decodeIntForKey: @"FoointVal"];
     floatVal = [decoder decodeFloatForKey: @"FoofloatVal"];
    } else {
     strVal = [[decoder decodeObject] retain];
     [decoder decodeValueOfObjCType: @encode(int) at: &intVal];
     [decoder decodeValueOfObjCType: @encode(float) at: &floatVal];
  }

  return self;
}
@end

After testing for keyed archiving, the three messages


[encoder encodeObject: strVal forKey: @"FoostrVal"];
[encoder encodeInt: intVal forKey: @"FoointVal"];
[encoder encodeFloat: floatVal forKey: @"FoofloatVal"];

archive the three instance variables from the object. The encodeObject:forKey: method encodes an object and stores it under the specified key for later retrieval using that key. The key names are arbitrary, so as long you use the same name to retrieve the data as when you unarchived it, you can specify any key you like. The only time a conflict might arise is if the same key is used for a subclass of an object being encoded. To prevent this from happening, you can insert the class name in front of the instance variable name when composing the key for the archive, as was done in Program 19.9.

You use the method encodeInt:forKey: instead of encodeValueOfObjCType:at:, which you need to use for unkeyed archives. Table 19.1 depicts the various encoding and decoding methods you can use for keyed archives.

The process of decoding keyed objects is straightforward: You use decodeObject:forKey: for Objective-C objects and the appropriate method from Table 19.1 for basic data types.

Table 19.1. Encoding and Decoding Basic Data Types in Keyed Archives

Encoder

Decoder

encodeBool:forKey:

decodeBool:forKey:

encodeInt:forKey:

decodeInt:forKey:

encodeInt32:forKey:

decodeInt32:forKey:

encodeInt64: forKey:

decodeInt64:forKey:

encodeFloat:forKey:

decodeFloat:forKey:

encodeDouble:forKey:

decodeDouble:forKey:

Some of the basic data types, such as char, short, long, and long long, are not listed in Table 19.1. You'll have to determine the size of your data object and use the appropriate routine. For example, a short int is normally 16 bits, an int and long are 32 bits, and a long long is 64 bits. (You can use the sizeof operator as described in Chapter 13, "Underlying C Language Features," to determine the size of any data type.) So, to archive a short int, store it in an int first and then archive it with encodeInt:forKey:. Reverse the process to get it back: Use decodeInt:forKey: and then assign it to your short int variable.

The test program and output from the keyed archiving example is shown in Program 19.9.

Program 19.9 Test Program
#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>
#import <Foundation/NSKeyedArchiver.h>
#import <Foundation/NSCoder.h>


int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  Foo *myFoo1 = [[Foo alloc] init];
  Foo *myFoo2;

  // First set and archive myFoo1 to a file
  [myFoo1 setAll: @"This is the string" iVal: 12345 fVal: 98.6];
  [NSKeyedArchiver archiveRootObject: myFoo1 toFile: @"foo.karch"];

  // Now restore the archive into myFoo2
  myFoo2 = [NSKeyedUnarchiver unarchiveObjectWithFile:
                       @"foo.karch"];
  printf ("%s\n%i\n%g\n", [[myFoo2 strVal] cString],
         [myFoo2 intVal], [myFoo2 floatVal]);

  [myFoo1 release];
  [pool release];
  return 0;
}
Program 19.9 Output
This is the string
12345
98.6
    [ Team LiB ] Previous Section Next Section