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.

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:

1MyClass* 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.

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.

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.255.187