您的位置:首页 > 其它

Brief Intro to Archives and Serialization of Foundation Framework

2013-11-21 12:00 603 查看
Archives and Serialization

The Foundation Framework archives and serialization classes implement mechanisms for converting an object (i.e., an object graph) into an architecture-independent byte buffer.
This data can then be written to a file or transmitted to another process, potentially over a network. Later, the data can be converted back into objects, preserving the associated object graph. In this way, these classes provide a lightweight means of data
persistence. The serialization process preserves the data and the positions of objects in an object hierarchy, whereas the archiving process is more general purpose—it preserves the data, data types, and the relations between the objects in an object hierarchy.

Archiving

NSCoder is an abstract class that declares the interface used to bothmarshal and unmarshall object
graphs. The marshalling process converts an object’s information into a series of bytes, and the unmarshalling process creates an object from a (previously marshalled) series of bytes.NSCoder includes
methods for encoding and decoding data of various types, testing an NSCoder instance, and provides support for secure coding. The
Foundation Framework includes four concrete subclasses ofNSCoder: NSArchiver, NSUnarchiver, NSKeyedArchiver,
andNSKeyedUnarchiver.

Sequential Archives

NSArchiver and NSUnarchiver are used to create sequential archives,
which means that the objects and values of a sequential archive must be decoded in the same order that they were encoded. In addition, when decoding a sequential archive, the entire object graph must be decoded.NSArchiver is
used to encode objects for writing to a file or some other use, and NSUnarchiver is used to decode objects from an archive.NSArchiver includes
methods for initialization, archiving data, retrieving archived data, and substituting classes or objects in an archive.NSUnarchiver includes methods for initialization,
decoding objects, substituting classes or objects, and management.

Keyed Archives

Whereas NSArchiver and NSUnarchiver are sequential
archives,NSKeyedArchiver and NSKeyedUnarchiver are keyed archives—each
value in the archive can be individually named/keyed. The key must be unique within the scope of the object in which the value is being encoded/decoded. When decoding a keyed archive, the values can be decoded out of sequence or not at all. Hence,
keyed archives provide better support for forward and backward compatibility and are recommended over the sequential archiving classes. NSKeyedArchiverincludes
methods for initialization, archiving data, encoding data and objects, and management. NSKeyedUnarchiver includes methods for initialization, unarchiving data, decoding
objects, and management.

The code shown in Listing
12-5 uses the NSKeyedArchiverarchiveRootObject: method to archive an NSString instance
namedgreeting to a file in the current directory named greeting.archive.

Listing
12-5. Archiving an Object with NSKeyedArchiver
NSString *greeting = @"Hello, World!";
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
NSString *archivePath = [cwd stringByAppendingString:@"/greeting.archive"];
BOOL result = [NSKeyedArchiver archiveRootObject:greeting toFile:archivePath];


The next code fragment uses the NSKeyedUnarchiverunarchiveObjectWithFile: method to decode an NSString object
named greeting from an archive stored in the file archivePath.
NSString *greeting = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];


Encoding and Decoding Objects

While the NSKeyedArchiver and NSKeyedUnarchiver classes
are responsible for the archiving process, a class must conform to theNSCoding protocol
to support enconding/decoding of class instances. This protocol declares two methods, encodeWithCoder: and initWithCoder:,
that encode/decode an object’s state (e.g., its properties and instance variables). When a coder object (i.e., an NSKeyedArchiver orNSKeyedUnarchiver instance)
archives an object, it instructs the object to encode its state by invoking its encodeWithCoder: method. Hence, a class must implement the appropriate encode and decode
method(s) because these will be called by the selected coder object. Listing
12-6depicts an implementation of a class named MyType that conforms to theNSCoding protocol.

Listing
12-6. Implementing the NSCoding Protocol Methods
@interface MyType : NSObject <NSCoding>

@property NSString *type;

@end

@implementation MyType

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.type forKey:@"TYPE_KEY"];
}

- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super init]))
{
type = [coder decodeObjectForKey:@"TYPE_KEY"];
}
return self;
}

@end


Property List Serialization

Property list serialization provides a means of converting a property list, a structured collection of data organized as name-value pairs, to/from an architecture-independent
byte stream. The Foundation FrameworkNSPropertyListSerialization class provides methods
to serialize and deserialize property lists directly and validate a property list. It also supports conversion of property lists to/from XML or an optimized binary format. In
contrast to archiving, basic serialization does not record the data type of the values nor the relationships between them; only the values themselves are recorded. Hence, you must write your code to deserialize data with the proper types and in the
proper order.

A property list organizes data as named values and collections of values. They are frequently used to store, organize, and access standard types of data. Property lists can be created programmatically, or more commonly, as XML files.

XML Property Lists

Property lists are most commonly stored in XML files, referred to as XML plist files. The NSArray and NSDictionary classes
both have methods to persist themselves as XML property list files and to create class instances from XML plists.

NSPropertyListSerialization

The NSPropertyListSerialization class
enables the programmatic creation of property lists. This class supports the following Foundation data types (the corresponding Core Foundation toll-free bridged data types are provided in parentheses):

NSData (CFData)
NSDate (CFDate)
NSNumber: integer, float, and Boolean values (CFNumber,CFBoolean)
NSString (CFString)
NSArray (CFArray)
NSDictionary (CFDictionary)

Because the supported data types include the collection classes NSArrayand NSDictionary,
each of which can contain other collections, anNSPropertyListSerialization object can be used to create hierarchies of data. As a property list is structured as a collection
of name-value pairs, a dictionary is used to programmatically create property list data.Listing
12-7 demonstrates the use of the instance methoddataWithPropertyList:format:options:error: to serialize anNSDictionary property
list collection of name-value pairs to a data buffer named plistData.

Listing
12-7. Property List Serialization of Name-Value Pairs
NSError *errorStr;
NSDictionary *data = @{ @"FirstName" : @"John", @"LastName" : @"Doe" };
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:data
format:NSPropertyListXMLFormat_v1_0
options:0
error:&errorStr];


The format: parameter specifies the property list format, of type enumNSPropertyListFormat.
The allowed values are as follows:

NSPropertyListOpenStepFormat: Legacy ASCII property list format.
NSPropertyListXMLFormat_v1_0: XML property list format.
NSPropertyListBinaryFormat_v1_0: Binary property list format.

The options: parameter
is meant to specify the selected property list write option. This parameter is currently unused and should be set to 0. If the method does not complete successfully, an NSError object
is returned in the error: parameter that describes the error condition.Listing
12-8 demonstrates use of thepropertyListWithData:options:format:error: method to deserialize a property list from the plistData data
buffer of Listing 12-7.

Listing
12-8. Property List Deserialization
NSError *errorStr;
NSDictionary *plist = [NSPropertyListSerialization
propertyListWithData:plistData
options:NSPropertyListImmutable
format:NULL
error:&errorStr];


The options: parameter specifies
the property list read option. This value can be any of those for the enum typeNSPropertyListMutabilityOptions. The possible values are as follows:

NSPropertyListImmutable: The returned property list contains immutable objects.
NSPropertyListMutableContainers: The returned property list has mutable containers but immutable
leaves.
NSPropertyListMutableContainersAndLeaves: The returned property list has mutable containers
and mutable leaves.

The format: parameter contains the format that the property list was stored in. If the value NULL is
provided, then it is not necessary to know the format. The possible non-NULL values for the format are of enum type NSPropertyListFormat.

Property list serialization does not preserve the full class identity of its objects, only its general kind. In other words, a property list object may be any of the preceding supported types. When a collection class is stored as
a property list, its elements must also be in the list of supported property list data types. In addition, the keys forNSDictionary property
list objects must be of type string (NSString). As
a result, if a property list is serialized and then deserialized, the objects in the resulting property list might not be of the same class as the objects in the original property list. In particular, when a property list is serialized, the mutability of the
container objects (i.e., NSDictionaryand NSArray objects) is not preserved. When
deserializing, though, you can choose to have all container objects created mutable or immutable.

Property list serialization also does not track the presence of objects referenced multiple times. Each reference to an object within the property list is serialized separately, resulting in multiple instances when deserialized.

Archiving an Object Graph

OK, now that you have a good handle on the archiving and serializationclasses, you’re going to create a program that demonstrates use of theFoundation
Framework Archiving APIs. This program will create an object graph from a class hierarchy and then encode and decode the object graph from an archive. The classes that
you’ll develop are diagrammed in Figure
12-1.



Figure
12-1. ArchiveCat program class hierarchy

As shown in Figure 12-1,
the program consists of a class hierarchy (theSubfamily-Family-Order classes)
and a class (Archiver) that’s used to archive instances of this hierarchy. In Xcode, create a new project by selecting New

Project
. . .
from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In theProject Options window, specify ArchiveCat for the Product Name, choose Foundation for
the Project Type, and select ARC memory management by selecting the Use Automatic Reference Countingcheck box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and
enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Next, you’re going to create the class hierarchy for the object graph. You’ll start with the base class and then successively implement the remaining subclasses. Select New

File
. . .
from the Xcode File menu, select the Objective-C class template, and name the class Order. Select the ArchiveCat folder for the files location and the ArchiveCat project as
the target, and then click the Create button. Next, in the Xcode project navigator pane, select the resulting header file named Order.hand update the interface, as shown
in Listing 12-9.

Listing
12-9. Order Interface
#import <Foundation/Foundation.h>

@interface Order : NSObject <NSCoding>

@property (readonly) NSString *order;

- (id)initWithOrder:(NSString *)order;

@end


The Order interface adopts the NSCoding protocol,
as required for classes that support archiving. The read-only property order identifies the order group in biological classification. The initWithOrder: method
initializes an Order object, setting the property to the input parameter value. Now select the Order.m file and update the implementation, as shown in Listing
12-10.

Listing
12-10. Order Implementation
#import "Order.h"

@implementation Order

- (id)initWithOrder:(NSString *)order
{
if ((self = [super init]))
{
_order = order;
}

return self;
}

- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super init]))
{
_order = [coder decodeObjectForKey:@"ORDER_KEY"];
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.order forKey:@"ORDER_KEY"];
}

- (NSString *) description
{
return [NSString stringWithFormat:@"Order:%@", self.order];
}

@end


The initWithOrder: implementation is very similar to init methods
you’ve developed elsewhere in this book. It simply assigns the orderinput parameter to the order property’s backing instance variable.

The initWithCoder: method, declared by the NSCoding protocol,
initializes the object using the archived state. Its input parameter, coder, is the NSCoder instance
used to decode the Order instance archive. The superclass of Order is NSObject; because NSObject doesn’t
adopt theNSCoding protocol,
the self variable
is assigned the returned value of the superclass init call.
self = [super init]


Next, the Order class state (represented by its properties and instance variables) is decoded and initialized. As the Order class
has a single property named order, the property’s instance variable is assigned to the value decoded by the decodeObjectForKey: method,
where the key is named ORDER_KEY.

The encodeWithCoder: method is used to archive the Order class
state, its input parameter, coder, is the NSCoder instance used to encode theOrder instance
archive. Because the superclass of Order doesn’t
adopt the NSCoding protocol,
this method doesn’t invoke the superclass’sencodeWithCoder: method,
but just encodes the Order class
state.Specifically, the method invokes the encodeWithCoder: method on the coder for each property/variable that needs to be archived.
[coder encodeObject:self.order forKey:@"ORDER_KEY"];[/code]

Finally, the class overrides the description method (inherited from its superclass) to return a text string listing the value for the order property.

Now you’ll implement the next class in the hierarchy. Select New

File
. . .
from the Xcode File menu, select the Objective-C class template, and name the class Family. Select the ArchiveCat folder for the files location and the ArchiveCat project as
the target, and then click theCreate button. Next, in the Xcode project navigator pane, select the resulting header file named Family.h and update the interface, as shown
in Listing 12-11.

Listing
12-11. Family Interface
#import "Order.h"

@interface Family : Order

@property(readonly) NSString *family;

- (id)initWithFamily:(NSString *)family order:(NSString *)order;

@end


The Family interface
subclasses the Order class,
and hence adopts theNSCoding protocol. The
read-only property family specifies the family group in a biological classification. The initWithFamily:order: method
initializes a Family object, setting the family and order properties to the input parameter values provided. Now select the Family.m file and update the
implementation, as shown in Listing
12-12.

Listing
12-12. Family Implementation
#import "Family.h"

@implementation Family

- (id)initWithFamily:(NSString *)family order:(NSString *)order
{
if ((self = [super initWithOrder:order]))
{
_family = family;
}

return self;
}

- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super initWithCoder:coder]))
{
_family = [coder decodeObjectForKey:@"FAMILY_KEY"];
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[super encodeWithCoder:coder];
[coder encodeObject:self.family forKey:@"FAMILY_KEY"];
}

- (NSString *) description
{
return [NSString stringWithFormat:@"Family:%@, %@", self.family,
[super description]];
}

@end


This implementation is very similar to that of the Order class, so you’ll just focus on the key differences. The initWithFamily:order: invokes
the superclass initWithOrder: method to initialize the superclass state properly, and then assigns the family input
parameter to the property’s backing instance variable.

The initWithCoder: method is very similar to that provided for theOrder class
(as shown in Listing 12-10). However, as the
superclass of the Family class (Order) adopts the NSCoding protocol,
the self variable is assigned the returned value of the superclass initWithCoder: call.
self = [super initWithCoder:coder]


In this way, the superclass state (the order property) is initialized properly. Next, the Family class
state (represented by its properties and instance variables) is decoded and initialized. As the Family class has a single property named family,
the property’s instance variable is assigned to the value decoded by the coder’s decodeObjectForKey:method, where the key is named FAMILY_KEY.

The encodeWithCoder: method is used to archive the Family class
state. Because the superclass of Family (the Order class) adopts the NSCodingprotocol,
this method first invokes invoke the superclass’sencodeWithCoder: method. Next, it invokes the encodeWithCoder:method
on the coder for each property/variable that needs to be archived; in this case, the family property.

As with the Order class, the description method returns
a text string consisting of the value of the family property concatenated with the value of the description for its superclass.
return [NSString stringWithFormat:@"Family:%@, %@", self.family,
[super description]];


Now you’ll implement the final class in the hierarchy. Select New

File
. . .
from the Xcode File menu, select the Objective-C class template, and name the class Subfamily. Select the ArchiveCat folder for the files location and the ArchiveCat project
as the target, and then click theCreate button. Next, in the Xcode project navigator pane, select the resulting header file named Subfamily.h and update the interface, as
shown in Listing
12-13.

Listing
12-13. Subfamily Interface
#import "Family.h"

@interface Subfamily : Family

@property(readonly) NSString *genus;
@property(readonly) NSString *species;

- (id)initWithSpecies:(NSString *)species
genus:(NSString *)genus
family:(NSString *)family
order:(NSString *)order;

@end


The Subfamily interface subclasses the Family class.
The read-only properties genus and species specifies the genus and species for
an animal group in a biological classification. TheinitWithSpecies:family:order: method initializes a Subfamily object,
similar to the corresponding methods for the Family and Order classes. Now select
the Subfamily.m file and update the implementation, as shown in Listing
12-14.

Listing
12-14. Subfamily Implementation
#import "Subfamily.h"

@implementation Subfamily

- (id)initWithSpecies:(NSString *)species
genus:(NSString *)genus
family:(NSString *)family
order:(NSString *)order
{
if ((self = [super initWithFamily:family order:order]))
{
_species = species;
_genus = genus;
}

return self;
}

- (id)initWithCoder:(NSCoder *)coder
{
if ((self = [super initWithCoder:coder]))
{
_species = [coder decodeObjectForKey:@"SPECIES_KEY"];
_genus = [coder decodeObjectForKey:@"GENUS_KEY"];
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[super encodeWithCoder:coder];
[coder encodeObject:self.species forKey:@"SPECIES_KEY"];
[coder encodeObject:self.genus forKey:@"GENUS_KEY"];
}

- (NSString *) description
{
return [NSString stringWithFormat:@"Animal - Species:%@ Genus:%@, %@",
self.species, self.genus, [super description]];
}

@end


This implementation is very similar to that of the Family class, differing primarily in the Subfamily class
state (the genus and speciesproperties). In all other respects, the logic is identical,
as you’ll see if you compare Listing 12-12 and Listing
12-14. Now you’ll implement the class used to archive the hierarchy. Select New

File
. . .
from the Xcode File menu, select the Objective-C class template, and name the classArchiver. Select the ArchiveCat folder for the files location and theArchiveCat project as
the target, and then click the Create button. Next, in the Xcode project navigator pane, select the resulting header file named Archiver.h and update the interface, as shown
in Listing 12-15.

Listing
12-15. Archiver Interface
#import <Foundation/Foundation.h>

@interface Archiver : NSObject

@property (readwrite) NSString *path;

- (BOOL) encodeArchive:(id)data toFile:(NSString *)file;
- (id) decodeArchiveFromFile:(NSString *) file;

@end


The Archiver interface
has a single property, path,
which defines the path for the file the archive is written to. The methodsencodeArchive:toFile: and decodeArchiveFromFile: are
used to encode/decode an archive to/from a file on the file system. Now select the Archiver.m file and update the implementation, as shown in Listing
12-16.

Listing
12-16. Archiver Implementation
#import "Archiver.h"

@implementation Archiver

- (id) init
{
if ((self = [super init]))
{
_path = NSTemporaryDirectory();
}

return self;
}

- (BOOL) encodeArchive:(id)objectGraph toFile:(NSString *)file
{
NSString *archivePath = [self.path stringByAppendingPathComponent:file];

// Create an archiver for encoding data
NSMutableData *mdata = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]
initForWritingWithMutableData:mdata];

// Encode the data, keyed with a simple string
[archiver encodeObject:objectGraph forKey:@"FELINE_KEY"];
[archiver finishEncoding];

// Write the encoded data to a file, returning status of the write
BOOL result = [mdata writeToFile:archivePath atomically:YES];
return result;
}

- (id) decodeArchiveFromFile:(NSString *) file
{
// Get path to file with archive
NSString *archivePath = [self.path stringByAppendingPathComponent:file];

// Create an unarchiver for decoding data
NSData *data = [[NSMutableData alloc] initWithContentsOfFile:archivePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]
initForReadingWithData:data];

// Decode the data, keyed with simple string
id result = [unarchiver decodeObjectForKey:@"FELINE_KEY"];
[unarchiver finishDecoding];

// Return the decoded data
return result;
}

@end


As shown in Listing
12-16, the init method sets the value for the path property. It uses the Foundation NSTemporaryDirectory() function
to create a path to the user’s temporary directory on the file system, and assigns that value to the property’s backing instance variable.

The encodeArchive:toFile: method encodes an object graph to a file. It creates a file path by prepending the
path property to the input file string. It then creates a mutable data object for archiving the graph. Next, it creates an NSKeyArchiver instance to perform the archiving,
initialized with the data object. It encodes the graph to the data object with the key FELINE_KEY, and then finishes the encoding. Finally, it writes the archived data
object to the file, returning a Boolean that indicates the success/failure of the write.

The decodeArchiveFromFile: method decodes an archive from a file, returning the initialized object graph. It creates a file path by prepending
the path property to the input file string. It then creates a data object for unarchiving the graph. Next, it creates an NSKeyUnarchiver instance to perform the unarchiving,
initialized with the data object. It decodes the graph to a data object with the key FELINE_KEY, finishes the decoding, and then returns the initialized data object.

And that’s it! Now that you have implemented the class hierarchy and the archiver class, let’s use this to archive an object graph. In the Xcode
project navigator, select the main.m file and update the main()function, as shown in Listing
12-17.

Listing
12-17. ArchiveCat main( ) Function
#import <Foundation/Foundation.h>
#import "Archiver.h"
#import "Subfamily.h"

int main(int argc, const char * argv[])
{
@autoreleasepool
{
// Create an Archiver to encode/decode an object graph
Archiver *archiver = [[Archiver alloc] init];

// Create an object graph and archive it to a file
id animal = [[Subfamily alloc] initWithSpecies:@"Lion"
genus:@"Panther"
family:@"Felid"
order:@"Carnivore"];
NSLog(@"\n%@", [animal description]);
NSString *file = @"data.archive";

// Display results
if ([archiver encodeArchive:animal toFile:file])
{
NSLog(@"You encoded an archive to file %@",
[[archiver path] stringByAppendingString:file]);
}

// Decode object graph from archive and log its description
id data = [archiver decodeArchiveFromFile:file];
if ([archiver decodeArchiveFromFile:file])
{
NSLog(@"You decoded an archive from file %@\n%@",
[[archiver path] stringByAppendingString:file], [data description]);
}

}
return 0;
}


As shown in Listing
12-17, the main() function begins by creating anArchiver object. It then creates
an object graph, logs its description to the output pane, and names the archive file. Next, the graph is archived to the named archive file, and if successful, a message is logged to the console.

The next set of statements decodes an object graph from the archive. First, it decodes the archive using the Archiver decodeArchiveFromFile:method.
It then performs a condition check on the result of the method call, and if it returns an object graph (meaning the operation completed successfully), it logs a description of the object graph to the output pane. Now when you compile and run the ArchiveCat
program, you should observe messages in the output pane comparable to those shown inFigure
12-2.



Figure
12-2. ArchiveCat program output

As shown in the output pane, a Subfamily object (i.e., object graph) is created and initialized with its input parameters, and its description
is logged to the output pane. Next, the object is archived to the specified file, and the full path of the archive file is logged to the output pane. A corresponding object is then decoded from the archive and its description is logged to the output pane.
As the description for the initially created object and the object decoded from the archive is identical, this validates that the archive was correctly encoded and decoded. This demonstrates use of the Archiving APIs to encode/decode an archive. Look this
code over for a while and make sure that you have a good handle on the archiving process. When you’re ready, let’s move on to distributed objects.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: