1.13 Archiving Objects
Saving and restoring
objects is made easier by
Objective-C's facilities for
reflection—inspecting
at runtime the structure of instances and classes. Objects can be
pre-designed at build-time, encoded, and saved as resources for
reconstruction at runtime. The runtime state of objects can similarly
be saved and restored in documents or other files. An
object's values are stored along with type
information necessary to restore a fully functioning instance.
1.13.1 Archiving Descendants of Object
To save and restore descendants of
Object, you can use its methods
-write: and
-read:, along with some
functions provided by the runtime and a helper class called
TypedStream.
For example, suppose your class declares an interface like this:
@interface MyClass : Object {
AnotherClass * obj ;
int i ;
}
...
@end
To add the fields that MyClass declares to
a stream that will be written to an archive, implement the following
method:
1 -(id )write:(TypedStream*)stream {
2 [super write:stream ];
3 objc_write_types(stream , "@i", obj , &i );
4 return self ;
5 }
Line 1. Override the root class method -write:.
Line 2. Call the parent class method to write the fields declared in
the parent.
Line 3. Call the runtime function objc_write_types(
).
The second parameter is a concatenation of descriptors for the types
of fields you are writing. These are one-character strings, the same
as those used by the @encode directive. They
are listed in objc-api.h.
To read the fields of MyClass from a
stream that has been read from an archive, implement the following
method:
1 -(id )read:(TypedStream*)stream {
2 [super read:stream ];
3 objc_read_types(stream, "@i", obj , &i );
4 return self ;
5 }
Line 1. Override the root class method -read:.
Line 2. Call the parent class method to read the fields declared in
the parent.
Line 3. Call the runtime function objc_read_types(
).
The second parameter is a concatenation of descriptors for the types
of the fields you are reading. These are one-character strings, the
same as those used by the @encode directive,
and declared in objc-api.h.
Line 4. Return the instance once it's been read.
To use these methods, you create an instance of
TypedStream and pass it to your
object's -read: and
-write: methods. For example, this code will save
an object to a file on disk:
1 MyClass* obj = [MyClass new];
2 TypedStream* stream =
3 objc_open_typed_stream_for_file("storage",
4 OBJC_WRITEONLY);
5 [obj write:stream];
6 objc_close_typed_stream(stream);
Line 1. Create the object in whatever way you choose.
Line 2. Create a TypedStream instance.
Line 3. Call the runtime function
open_typed_stream_for_file(
).
The first parameter is a filename.
Line 4. Here the second parameter to the
objc_open_typed_stream_for_file method specifies that you will be
writing to a file.
Line 5. Pass the stream to the object's
-write: method. The object's
declared and inherited fields are written to a file.
Line 6. Close the stream.
To retrieve the saved object, you can use the following code:
1 TypedStream* stream =
2 objc_open_typed_stream_for_file("storage",
3 OBJC_READONLY);
4 MyClass* obj = [[MyClass alloc] read:stream];
5 objc_close_typed_stream(stream);
Line 1. Create a TypedStream instance.
Line 2. Call the runtime function
open_typed_stream_for_file( ). The first parameter
is a filename.
Line 3. Here the second parameter to the
objc_open_typed_stream_for_file method specifies
that you will be reading from a file.
Line 4. Allocate space for your object, then set its fields by
passing the stream to the object's
-read: method.
Line 5. Close the stream.
The GNU runtime provides more functions for managing object storage
and retrieval. See the documentation for your distribution for
more information.
1.13.2 Archiving Descendants of NSObject
The Cocoa framework uses the term
"coding" for the process of
translating objects to a saveable form. Cocoa declares an
NSCoding protocol, and provides an
NSCoder class that saves and restores objects
that implement the protocol. NSObject does not
itself implement NSCoding; to use archiving, you
must implement the NSCoding protocol in your
classes.
The NSCoding protocol consists of two methods:
initWithCoder: and
encodeWithCoder:. These
exhibit the same structure as initializers: you first send the same
message to super, then proceed to encode or
decode the fields of your object, following the same order in each
method. Your objects don't have to pay any more
attention than that to the details of constructing or interpreting an
object's stored form.
 |
If -initWithCoder: were named
-decodeWithCoder: its relation to
-encodeWithCoder: would be more clear. But
-initWithCoder: is an initializer and follows the
Objective-C convention by starting with init.
|
|
For example, suppose your class declares an interface like this:
@interface MyClass : NSObject {
AnotherClass * obj ;
int i ;
}
...
@end
To save the fields of MyClass, implement
the following method:
1 -(void )encodeWithCoder:(NSCoder*)coder {
2 // [super encodeWithCoder:coder ];
3 [coder encodeObject:obj ];
4 [coder encodeValueOfObjCType:@encode (int )
5 at:&i ];
6 }
Line 1. Declare the method as specified by the
NSCoding protocol. Cocoa's
archiving methods will pass in the coder object to your encode
method.
Line 2. Call super only if the parent class also implements
the NSCoding protocol. In this example, the parent
class is NSObject, which doesn't
implement the protocol, so this line of code is commented out.
Line 3. If the field is an Objective-C object, pass it to the coder
using the -encodeObject: method.
Line 4. If the field is a C type, use the
-encodeValueOfObjCType method shown here. The
@encode directive constructs a string
describing the structure of the specified type, which tells the coder
how to process the value. The coder stores the string along with the
value to facilitate decoding.
Line 5. Instead of directly passing the value you want to store, you
pass its address.
To read the fields of MyClass, implement
the following method:
1 -(void )initWithCoder:(NSCoder*)coder {
2 if (self = [super initWithCoder:coder ] {
3 int version =
4 [coder versionForClassName:@"MyClass "];
5 // Check version here.
6 obj = [[coder decodeObject] retain];
7 [coder decodeValueOfObjCType:@encode (int )
8 at:&i ];
9 }
10 }
Line 1. Declare the method to conform to the
NSCoding protocol. Cocoa's
archiving methods will pass in the coder object to your decoding
method.
Line 2. Call super only if the parent class
also implements the NSCoding protocol. If the
parent class does not implement the NSCoding
protocol, call its designated initializer instead. In either case,
assign the result to self because the parent
class may return a different object than itself.
Lines 3, 4. Get the class's version number, which
the coder automatically stores with the object information.
Line 5. Depending on the version number, you may have to vary the
implementation of the rest of the method.
Line 6. To retrieve an Objective-C object, call the
coder's
-decodeObject method. You must decode objects in the
same order you encoded them in the
-encodeWithCoder: method.
When you get a value from -decodeObject, you
should call -retain on it. The
NSCoder will also be retaining the value until it
is no longer needed by that class, at which time
NSCoder will release it.
Line 7. To retrieve a field that is a C type, use the form shown
here. The @encode directive constructs a
string telling the coder about the structure of the value it will be
decoding.
Line 8. You must pass the address of the value you are restoring.
To use the archiving framework, the data you save and restore needs
to have a root object. This isn't the same
as a root class, but means an object from which all other objects are
reachable through pointers. If you implement the
NSCoding protocol as described here, the coding
process will traverse all the connections and encode or decode all
the data.
You set the encoding or decoding process off by calling class methods
of NSArchiver and
NSUnarchiver. For example, to encode data starting
with object rootObj:
NSData* encoded =
[NSArchiver archivedDataWithRootObject:rootObj];
To decode data back into a root object:
rootObj =
[NSUnarchiver unarchiveObjectWithData:encoded];
The archiver objects create the
NSCoder object and pass it to the
NSCoding protocol methods of your objects.
There are many ways you can use the archiving framework, but the most
common is to save and restore data using an
NSDocument subclass. This class provides several
methods for saving and restoring data through a root object. Most
commonly, you will override
-dataRepresentationOfType: and
-loadDataRepresentation:ofType: to call the archiver and unarchiver
methods. See your Cocoa documentation for more information on
saving and restoring
documents.
|