iosobjective-ccocoanscodingnscoder

Pointers to existing objects after encodeWithCoder: and initWithCoder:


How does NSCoding deal with encoding and decoding of pointers to other objects rather than values? I have a range of model classes which I need to encode, and they need to reference each other with weak properties. I was surprised to find that the references seemed to be maintained after encoding, saving to a file, then subsequently reading from the file and decoding. This worked even though the memory addresses of the objects which referenced each other changed. I'm interested to know how NSCoding achieves this, and also want to ensure it will work consistently.

In short: if I encode properties containing pointers to other objects, can I rely 100% on the references being maintained after decoding?


Solution

  • tl;dr: Yes, NSKeyedArchiver and NSKeyedUnarchiver maintain the reference relationships of the objects in your object graph; if you encode an object graph where objects reference other objects, you can expect the graph to be decoded in a way that maintains those relationships. You won't necessarily get back the same pointers after encoding and decoding, but the identities of the objects will remain the same.


    When you encode an object via -encodeObject:forKey:, NSKeyedArchiver never writes out the pointer address of the object. Instead, it maintains a unique table of objects it's seen before:

    1. If the object you're encoding has never been seen before, it gets added to the table and assigned a UID. It then has -encodeWithCoder: called on it so it can encode all of its properties
    2. If it's been seen before (and has been assigned a UID), nothing happens (but the UID is recorded in its place)

    When archiving is complete, the archive contains the unique table of objects, along with another dictionary that represents the structure of the object graph. In place of objects are UIDs.

    On decode, when you go to call -decodeObject:forKey:, NSKeyedUnarchiver does the opposite mapping:

    1. It looks at the UID for the key in the archive and searches for the UID in the unique table. If that object has been decoded before, it returns a reference to the decoded object. This is the key part — you won't necessarily get the same pointer on decode that was encoded on encode, but you'll get a reference to the same object instead of a copy
    2. If the UID's never been decoded before, it looks for the encoded object in the unique table corresponding to the UID, looks up the class and calls +alloc, -initWithCoder:, and -awakeAfterUsingCoder on the object. The object is then recorded in the UID-to-object map to be reused if necessary

    This is one of the key features of the keyed archiver system, and you can rely on this behavior being maintained.