2.5 Archiving Objects

Archiving an object (or a collection of interconnected objects, known as a graph) into a NSData representation is often useful or necessary. Objects that are archived into an NSData object can be transported over network connections or interprocess communication channels and saved to the filesystem. Later, the original graph of objects can be reconstituted from the archive data.

Foundation provides five classes to support the creation and extraction of archives, all subclasses of NSCoder:

  • NSArchiver

  • NSUnarchiver

  • NSKeyedArchiver

  • NSKeyedUnarchiver

  • NSPortCoder

NSCoder declares the common interface for encoding and decoding objects and other Objective-C data types. For example, the encodeObject method encodes an object into an archive, and methods such as encodeInt: and encodeRect: support encoding C data types such as integers and common Cocoa data structures.

NSCoder does not implement these methods; it is an abstract class. Rather, subclasses implement the appropriate methods for their particular purpose. NSArchiver and NSUnarchiver provide a straightforward way of encoding and decoding objects and scalars, but they have limitations. The biggest limitation is that objects in an archive can be decoded only in the same order in which they were encoded. Because of this constraint, changing an encoding system is difficult once it has been established publicly.

2.5.1 Keyed Archiving

The NSKeyedArchiver and NSKeyedUnarchiver classes solve this problem by associating keys with each object and scalar encoded in an archive. Decoders can use these keys to access an archive's contents in a convenient order that isn't constrained by a design decision made in a previous version of the application.

Keyed archiving is not available in versions prior to Mac OS X 10.2. If your application supports Mac OS X 10.1, then check whether the coder passed in initWithCoder: or encodeWithCoder: supports keyed archiving. To do this, use the NSCoder method allowsKeyedCoding, which returns YES if keyed coding is supported, and NO otherwise.

The last NSCoder subclass, NSPortCoder, encodes and decodes object proxies in the distributed objects system. NSConnection uses this class, and as such, you should never have to interact with it. Chapter 6 discusses the distributed objects system in more detail.

Consider Examples Example 2-31 and Example 2-32. Example 2-31 shows the interface for a hypothetical Employee class. Note in this example that the interface declaration indicates that Employee conforms to the NSCoding protocol.

Example 2-31. Employee class with support for the NSCoding protocol
@interface Employee : NSObject < NSCoding > {
    NSString *firstName;
    NSString *lastName;
    int employeeNumber;
}
// Methods left out
@end

Example 2-32 shows a way to implement the NSCoding protocol for the Employee class.

Example 2-32. Implementing NSCoding in the Employee class
@implementation Employee

- (void)setFirstName:(NSString *)newName 
{
    [newName retain];
    [firstName autorelease];
    firstName = newName;
}

- (void)setLastName:(NSString *)newName
{
    [newName retain];
    [lastName autorelease];
    lastName = newName;
}

- (void)encodeWithCoder:(NSCoder *)encoder
{
    if ( [encoder allowsKeyedCoding] ) {
        [encoder encodeObject:firstName forKey:@"First"];
        [encoder encodeObject:lastName forKey:@"Last"];
        [encoder encodeInt: employeeNumber forKey:@"Number"];
    } else {
        [encoder encodeObject:firstName];
        [encoder encodeObject:lastName];
        [encoder encodeValueOfObjCType:@encode(int)
                                    at:&employeeNumber];
    }
}

- (id)initWithCoder:(NSCoder *)decoder
{
    if ( [decoder allowsKeyedCoding] ) {
        // These may be decoded in any order you like
        employeeNumber = [decoder decodeIntForKey:@"Number"];

        // Returned  values are autoreleased
        [self setFirstName: [decoder decodeObjectForKey:@"First"]];
        [self setLastName: [decoder decodeObjectForKey:@"Last"]];
    } else {
        // These must be decoded in the same order that they
         // were encoded
        [self setFirstName: [decoder decodeObject]];
        [self setLastName: [decoder decodeObject]];
        [decoder decodeValueOfObjCType:@encode(int)
                                    at:&employeeNumber];
    }
    return self;
}

@end


    Part II: API Quick Reference