Keyed archives are available only in Mac OS X version 10.2 and later. If your application must run in earlier versions, you cannot make use of keyed archives. There should typically be no need be no need to retrofit support for non-keyed archiving to a class originally written to support keyed archiving. Since non-keyed archiving is deprecated, you should add support for keyed archiving to classes that currently use only non-keyed archiving.
Updating a Class to Use Keyed Coding
Supporting Keyed and Non-Keyed Archiving
Converting Coding Methods After a Class Has Been Archived to Keyed Archives
Distinguishing an NSArchiver Archive From an NSKeyedArchiver Archive
Updating your classes to use keyed coding is not difficult. In most cases, your NSCoding
methods need to assign keys only to the values being encoded and decoded. Special issues come up if you still need to handle sequential archives. Here are recommendations for changing your class to do keyed coding:
Preserve current non-keyed coding behavior in public classes. NSArchiver
and NSUnarchiver
still exist to allow applications to read and write old-style archives. If other developers may have used your class, or subclassed it, you should keep the old encoding and decoding code for non-keyed coding, but execute the keyed coding code if the coder supports keyed coding. (See “Encoding and Decoding Objects.”)
Reassess whether private classes need to be codable. If a class doesn’t need to be codable, or shouldn’t be codable, but is currently implementing NSCoding
, you should remove the NSCoding
conformance from the interface declaration and remove the methods. If the class is encoded or decoded by other classes outside your control, however, you need to preserve the old behavior. You don’t have to support new keyed coding behavior, though. You can prevent the class from ever being encoded into a keyed archive by raising an exception if the coder supports keyed coding. Conversely, you may not want new classes to support non-keyed coding, in which case you can raise an exception if the coder does not support keyed coding.
Reassess what values are being encoded. If the old code was archiving stuff that is no longer needed, just for the sake of compatibility, now is a good time to stop doing that. Nominally, an initWithCoder:
method might become much simpler without the need to do old class version checking.
Convert old methods to new methods. There may not be exact matches for the old methods in the set of new keyed methods—choose something reasonable. Decide on names for each value, and don’t forget to put some sort of prefix or other “uniqueness guarantor” into the key strings. Do not forget to retain the return value of decodeObjectForKey:
, when converting code that used to decode the objects with the decodeValueOfObjCType:at:
, decodeValuesOfObjCTypes:
, and decodeArrayOfObjCType:count:at:
methods. Also note that the decodeBytesForKey:returnedLength:
method returns a pointer to bytes that cannot be mutated—you must make a copy if you want to change the bytes.
Freeze the old encoding and decoding methods along with the class version number. Any setVersion:
calls should be left in, so that the class version remains at its current value. Of course, you can keep updating the old non-keyed coding algorithms and changing the class version, as before, but users of your updated class should also be updating to keyed coding. The Cocoa classes, for example, will not be updating their non-keyed coding algorithms, even when new features and state are added—non-keyed archives will simply not save or get the new features.
The following example shows how you can implement archiving for a class "MapView" to support 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 lines that invoke the superclass’s encodeWithCoder:
and initWithCoder:
methods.
- (void)encodeWithCoder:(NSCoder *)coder { |
[super encodeWithCoder:coder]; |
if ([coder allowsKeyedCoding]) { |
[coder encodeObject:mapName forKey:@"MVMapName"]; |
[coder encodeFloat:magnification forKey:@"MVMagnification"]; |
[coder encodeObject:legendView forKey:@"MVLegend"]; |
[coder encodeConditionalObject:auxiliaryView forKey:@"MVAuxView"]; |
} |
else { |
[coder encodeObject:mapName]; |
[coder encodeValueOfObjCType:@encode(float) at:&magnification]; |
[coder encodeObject:legendView]; |
[coder encodeConditionalObject:auxiliaryView]; |
} |
} |
- (id)initWithCoder:(NSCoder *)coder { |
self = [super initWithCoder:coder]; |
if ([coder allowsKeyedCoding]) { |
// Can decode keys in any order |
mapName = [[coder decodeObjectForKey:@"MVMapName"] retain]; |
legendView = [[coder decodeObjectForKey:@"MVLegend"] retain]; |
auxiliaryView = [[coder decodeObjectForKey:@"MVAuxView"] retain]; |
magnification = [coder decodeFloatForKey:@"MVMagnification"]; |
} |
else { |
// Must decode keys in same order as encodeWithCoder: |
mapName = [[coder decodeObject] retain]; |
[coder decodeValueOfObjCType:@encode(float) at:&magnification]; |
legendView = [[coder decodeObject] retain]; |
auxiliaryView = [coder decodeObject]; |
} |
return self; |
} |
When unarchiving data from a sequential archive, the corresponding unarchiving code must follow exactly the same sequence of data types. Matching these is important, as the method originally used determines the format of the encoded data.
Because NSKeyedArchiver
also implements the non-keyed coding methods that it inherits from NSCoder
, a class that has not been updated for keyed coding can still be encoded into a keyed archive. This can happen if the application creating the archive has been updated to use NSKeyedArchiver
, but a class has not, so it still uses the old style encoding methods. The class’s instance variables are written to the archive without keys, just like for a sequential archive. If this may have happened for one of your classes, when you update your class, you must be able to handle the case where although the unarchiver supports keyed coding, the object’s instance variables were not encoded with keys. If this occurs, you must decode the values as if they are coming from a non-keyed archive. In other words, you must decode the values in the same sequence and with the matching non-keyed decoding methods as when encoded.
The simplest technique is to use a version key of some sort. When (after conversion), the object encodes itself, it needs to write a special keyed value which indicates that the object was encoded using keyed coding methods. At decoding time, if the coder allows keyed coding and this special key exists, then initWithCoder:
knows that not only is this a keyed archive, but keyed coding methods were also used. If the key does not exist, initWithCoder:
must still use the old decoding algorithm.
- (id)initWithCoder:(NSCoder *)coder { |
if ([coder allowsKeyedCoding] |
&& [coder containsValueForKey:@"UsesKeyedCoding"] ) { |
// Use keyed coding methods |
} |
else { |
// Use non-keyed coding methods |
} |
return self; |
} |
Ideally, you should use a different file extension for a new document format which is keyed-archiving-based rather than non-keyed-archiving-based. If this is not possible, they you can look at the first few bytes of the archived data (the “magic number”). If the data is at least 13 bytes long, and the 2nd-13th bytes are \00btypedstream
or \00bstreamtyped
then you have an old archive. A suitable test is illustrated in the following code fragment:
if (13 <= dataLength && |
((databytes[1] == 0xb && 0 == memcmp(databytes + 2, "typedstream", 11)) || |
(databytes[1] == 0xb && 0 == memcmp(databytes + 2, "streamtyped", 11)))) { |
// non-keyed archive ... |
© 2002, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-02-04)