To support encoding and decoding of instances, a class must adopt the NSCoding
protocol and implement its methods. This protocol declares two methods that are sent to the objects being encoded or decoded.
In keeping with object-oriented design principles, an object being encoded or decoded is responsible for encoding and decoding its instance variables. A coder instructs the object to do so by invoking encodeWithCoder:
or initWithCoder:
. The encodeWithCoder:
message instructs the object to encode its instance variables with the provided coder; an object can receive this method any number of times. The initWithCoder:
message instructs the object to initialize itself from data in the provided coder; as such, it replaces any other initialization method and is sent only once per object.
Encoding an Object
Decoding an Object
Performance Considerations
Making Substitutions During Coding
When to Retain a Decoded Object
Restricting Coder Support
When an object receives an encodeWithCoder:
message, it should encode all of its vital instance variables, after forwarding the message to its superclass if its superclass also conforms to the NSCoding
protocol. An object does not have to encode all of its instance variables. Some values may not be important to reestablish and others may be derivable from related state upon decoding. Other instance variables should be encoded only under certain conditions (for example, with encodeConditionalObject:
, as described in “Conditional Objects”).
For example, suppose you were creating a MapView class that displays a legend and a map at various magnifications. The MapView class defines several instance variables, including the name of the map and the current magnification. The MapView class also contains instance variables for several related views. The encodeWithCoder:
method of MapView might look like the following:
- (void)encodeWithCoder:(NSCoder *)coder { |
[super encodeWithCoder:coder]; |
[coder encodeObject:mapName forKey:@"MVMapName"]; |
[coder encodeFloat:magnification forKey:@"MVMagnification"]; |
[coder encodeObject:legendView forKey:@"MVLegend"]; |
[coder encodeConditionalObject:auxiliaryView forKey:@"MVAuxView"]; |
} |
Note: This example uses only keyed archiving. If instances of your class can be encoded to a keyed or sequential archive, see “Supporting Keyed and Non-Keyed Archiving.”
This example assumes that the superclass of MapView also supports the NSCoding
protocol. If the superclass of your class does not support NSCoding
, you should omit the line that invokes the superclass’s encodeWithCoder:
method.
The auxiliaryView
object is encoded conditionally in this example because MapView objects do not “own” their auxiliary view. MapView objects hold only a weak, non-retained, reference to the auxiliary view. Any weak references held by your objects will normally be encoded conditionally, too.
The encodeValueOfObjCType:at:
and encodeObject:
methods are coder methods that you can use to encode instance variables of your class. Use the keyed coding methods, such as encodeObject:forKey:
, only if the coder supports keyed coding. You can use these and other methods of the coder to encode id’s, scalars, C arrays, structures, strings, and pointers to any of these types. See the NSCoder
, NSArchiver
, NSUnarchiver
, NSKeyedArchiver
, and NSKeyedUnarchiver
class specifications for a list of methods.
The @encode()
compiler directive generates an Objective-C type code from a type expression that can be used as the first argument of encodeValueOfObjCType:at:
. See “Type Encodings” in The Objective-C 2.0 Programming Language for more information.
When an object receives an initWithCoder:
message, the object should first send a message to its superclass (if appropriate) to initialize inherited instance variables, and then it should decode and initialize its own instance variables. For non-keyed coding, the sequence of decode messages must be identical to the sequence of encode messages used in encodeWithCoder:
. For keyed coding, the keys can be decoded in any order. MapView’s implementation of initWithCoder:
might look like this:
- (id)initWithCoder:(NSCoder *)coder { |
self = [super initWithCoder:coder]; |
mapName = [[coder decodeObjectForKey:@"MVMapName"] retain]; |
legendView = [[coder decodeObjectForKey:@"MVLegend"] retain]; |
auxiliaryView = [[coder decodeObjectForKey:@"MVAuxView"] retain]; |
magnification = [coder decodeFloatForKey:@"MVMagnification"]; |
return self; |
} |
Note: This example uses only keyed archiving. If instances of your class can encoded to a keyed or sequential archive, see “Supporting Keyed and Non-Keyed Archiving.”
Note the assignment of the return value of initWithCoder:
to self
in the example above. This is done in the subclass because the superclass, in its implementation of initWithCoder:
, may decide to return an object other than itself. If the superclass of your class does not support NSCoding
, you should invoke the superclass’s designated initializer instead of initWithCoder:
.
Also note that the auxiliaryView
object is not retained after it is decoded. The MapView class encoded the object conditionally, because it only had a weak reference to the auxiliary view. If the auxiliary view does not decode to nil
, some other object in the archive had to encode the view object unconditionally and, presumably, will retain the object when it is decoded. Therefore, by not retaining it here, MapView objects restore their weak reference to the auxiliary view.
The less you encode for an object, the less you have to decode, and both writing and reading archives becomes faster. Stop writing out items which are no longer pertinent to the class (but which you may have had to continue writing under non-keyed coding).
Encoding and decoding booleans is faster and cheaper than encoding and decoding individual 1-bit bit fields as integers. However, encoding many bit fields into a single integer value can be cheaper than encoding them individually, but that can also complicate compatibility efforts later (see “Structures and Bit Fields”).
Don’t read keys you don’t need. You aren’t required to read all the information from the archive for a particular object, as you were with non-keyed coding, and it is much cheaper not to. However, unread data still contributes to the size of an archive, so stop writing it out, too, if you don’t need to read it.
It is faster to just decode a value for a key than to check if it exists and then decode it if it exists. Invoke containsValueForKey:
only when you need to distinguish the default return value (due to non-existence) from a real value that happens to be the same as the default.
It is more valuable for decoding to be fast than for encoding to be fast. If there is some trade-off you can make that improves decoding performance at the cost of lower encoding performance, the trade-off is usually reasonable to make.
You should avoid using “$” as a prefix for your keys. The keyed archiver and unarchiver use keys prefixed with “$” for internal values. Although they test for and mangle user-defined keys that have a “$” prefix, this overhead slows down archiving performance.
During encoding or decoding, a coder object invokes methods that allow the object being coded to substitute a replacement class or instance for itself. This allows archives to be shared among implementations with different class hierarchies or simply different versions of a class. Class clusters, for example, take advantage of this feature. This feature also allows classes that should maintain unique instances to enforce this policy on decoding. For example, there need only be a single NSFont
instance for a given typeface and size.
Substitution methods are declared by NSObject
, and come in two flavors: generic and specialized. These are the generic methods:
Method | Typical use |
---|---|
| Allows an object, before being encoded, to substitute a class other than its own. For example, the private subclasses of a class cluster substitute the name of their public superclass when being archived. |
| Allows an object, before being encoded, to substitute another instance in its place. |
| Allows an object, after being decoded, to substitute another object for itself. For example, an object that represents a font might, upon being decoded, release itself and return an existing object having the same font description as itself. In this way, redundant objects can be eliminated. |
The specialized substitution methods are analogous to classForCoder
and replacementObjectForCoder:
, but they are designed for (and invoked by) a specific, concrete coder subclass. For example, classForArchiver
and replacementObjectForPortCoder:
are used by NSArchiver
and NSPortCoder
, respectively. By implementing these specialized methods, your class can base its coding behavior on the specific coder class being used. For more information on these methods, see their method descriptions in the NSObject
class specification.
In addition to the methods just discussed, NSKeyedArchiver
and NSKeyedUnarchiver
allow a delegate object to perform a final substitution before encoding and after decoding objects. The delegate for an NSKeyedArchiver
object can implement archiver:willEncodeObject:
and the delegate for an NSKeyedUnarchiver
object can implement unarchiver:didDecodeObject:
to perform the substitutions.
You can decode an object value in two ways. The first is explicitly, using the decodeObject
or decodeObjectForKey:
method. When decoding an object explicitly you must follow the object ownership convention and retain the object returned if you intend to keep it. Otherwise, the object is owned by the coder and the coder is responsible for releasing the object.
The second means of decoding an object is implicitly, using the decodeValueOfObjCType:at:
method or one of its variants, decodeArrayOfObjCType:count:at:
and decodeValuesOfObjCTypes:
. These methods decode values directly into memory that you provide. In the case of objects, the value is the object pointer. As this memory is already owned by you, you are responsible for releasing the objects decoded into it. This behavior can prove useful for optimizing large decoding operations, as it obviates the need for sending a retain
message to each decoded object.
In some cases, a class may implement the NSCoding
protocol, but not support one or more coder types. For example, the classes NSDistantObject
, NSInvocation
, NSPort
, and their subclasses adopt NSCoding
only for use by NSPortCoder
within the distributed objects system; they cannot be encoded into an archive. In these cases, a class can test whether the coder is of a particular type and raise an exception if it isn’t supported. If the restriction is just to limit a class to sequential or keyed archives, you can send the message allowsKeyedCoding
to the coder; otherwise, you can test the class identity of the coder as shown in the following sample.
- (void)encodeWithCoder:(NSCoder *)coder { |
if ([coder isKindOfClass:[NSKeyedArchiver class]]) { |
// encode object |
} |
else { |
[NSException raise:NSInvalidArchiveOperationException |
format:@"Only supports NSKeyedArchiver coders"]; |
} |
} |
In other situations, a class may inherit NSCoding
from a superclass, but the subclass may not want to support coding. For example, NSWindow
inherits NSCoding
from NSResponder
, but it does not support coding. In these cases, the class should override the initWithCoder:
and encodeWithCoder:
methods so that they raise exceptions if they are ever invoked.
© 2002, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-02-04)