3

I was wondering how NSCoder would handle an object that was shared and encoded by multiple objects the next time it was decoded. Will it make two copies of the object, or will one object be decoded and shared between all other objects that decode it?

I've provided a small example of a situation like this below.

Example:

  1. Application starts up
  2. Object A and Object B set Object C as their delegate
  3. Application receives termination notification.
  4. Object A and Object B encode themselves and all of their objects (including their delegates)
  5. Application shuts down and restarts
  6. Object A and Object B decode themselves and all of their objects (including their delegates)

Would Object A and Object B both share the same decoded object after step 6 or would they each have their own copy of it?

jscs
  • 63,694
  • 13
  • 151
  • 195
Eric L
  • 640
  • 6
  • 20

3 Answers3

7

They'll share a reference to the same object (unless you go to lengths to change that behavior).

I.e. NSCoding can deal with fully cyclic, omni-directional, complexly connected, object graphs (as long as all graph participants correctly support NSCoding).

Note that encoding delegates is highly atypical. Delegates are usually connected to an unarchived object graph after anarchival and delegates act as a sort of conduit between your archived model layer (or archived view layer, in the case of IB -- the story is more complex, really, for XIB files... but... close enough) and the rest of your app.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • Yeah, I wouldn't usually encode a delegate. I just couldn't think of a better example on short notice. I suppose I could have used my actual situation but I was more typing than was needed. Anyways, Thanks! – Eric L Sep 27 '11 at 00:06
  • No worries -- just wanted to make sure. :) – bbum Sep 27 '11 at 02:21
5

This is a great question, and I always wondered about it myself. So, I wrote a small test program to try it out. Four classes: ClassA, ClassB, ClassC, and MyBaseClass. ClassA, ClassB, and ClassC inherit from MyBaseClass which conforms to NSCoding and provides two properties: name and age. The A, B, and C classes are identical except that ClassA and ClassB also contain a reference to an instance of ClassC. ClassA and ClassB also override initwithCoder: and encodeWithCoder: to decode and encode their references to the instance of ClassC. Here is what the top-level code looks like:

ClassA *a = [[ClassA alloc] initWithName:@"Mr. A" age:11];
ClassB *b = [[ClassB alloc] initWithName:@"Mrs. B" age:22];
ClassC *c = [[ClassC alloc] initWithName:@"Ms. C" age:33];

b.c = c;
a.c = c;

NSArray *rootObject = @[a, b, c];
NSString *const kFilePath = @"/Users/myname/Documents/testarchive";
BOOL result = [NSKeyedArchiver archiveRootObject:rootObject toFile:kFilePath];
NSLog(@"result = %@", (result) ? @"YES" : @"NO");

NSArray *newRootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:kFilePath];
NSLog(@"new root object = %@", newRootObject);

The objects serialize and deserialize perfectly. Also, after deserializing, a.c and b.c point to the same instance of ClassC — that is, their object pointers have the same address.

Apparently, within NSKeyedArchiver's encodeObject:forKey:, a test is done to see if the object being encoded isEqualTo: a previously encoded object, and if it is, a reference is stored instead of a complete object. The converse must happen in NSKeyedUnarchiver's decodeObject:forKey:. Very cool!

Steve
  • 154
  • 1
  • 4
-1

I don't think the first answer is entirely correct. According to Apple's documentation, "The serialization only preserves the values of the objects and their position in the hierarchy. Multiple references to the same value object might result in multiple objects when deserialized".

So it's not guaranteed that a single object serialized will result in a single object when deserialized from those multiple NSCoders.

If you're implementation is anything like your example then you may not be thinking about things quite right. If you think about the logical organization of an application it might make sense that multiple objects could share the same delegate. But generally I wouldn't expect somebody to use the NSCoder protocol to encode/decode delegates. Normally I would expect the delegate to encode/decode the objects for which it is the delegate.

For instance let's look at NSTableView. Perhaps the user gets the ability to configure how the NSTableView is displayed (perhaps the user is allowed to resize columns or choose which columns are displayed). This is useful information that you might want to save and restore using the NSCoding protocol. NSTableView's also have delegates. The delegate should be a controller (from the MVC paradigm) and should never really need to be encoded/decoded using NSCoding because it is generic code that does not have to maintain any runtime state.

So Ideally you create your delegate/controller using an init method. It realizes it needs to configure a NSTableView to look the way it did the last time the user configured it, so it pulls an old table view from disk using NSCoding and then displays that to the user just as it was the last time they saw it.

I think the same goes for the Model layer in the MVC Paradigm. Again, the Controller layer should be decoding the model objects which are specific to what the user has done through their use of the application.

It sounds more like you are trying to instantiate the controller layer from the model or perhaps view layer. It doesn't really make sense.

Carter
  • 4,738
  • 2
  • 22
  • 24
  • Fair points, but doesn't answer the question, which was about the behavior of the archiver. Could be a comment on the question or a brief note like e.g. bbum's answer. – Ben Zotto Sep 27 '11 at 00:12
  • 1
    "Multiple references to the same **value object** might result in multiple objects when deserialized". Those objects better had be -isEqual: and -hash the same. That should be clarified in the docs. – bbum Sep 27 '11 at 02:22
  • My answer was in the first paragraph. – Carter Sep 27 '11 at 11:12
  • 2
    The sentence you're quoting from Apple's documentation refers to "serialization" with e.g. `NSPropertyListSerialization`, not to "archiving" with `NSCoder`. This is made clear e.g. by this sentence: 'Because serialization does not preserve class information or mutability, nor handles multiple references, coding (as implemented by NSCoder and its subclasses) is the preferred way to make object graphs persistent.' Also, there is no mention of *multiple* NSCoders in Eric's question. – Stephan Tolksdorf Sep 06 '17 at 12:48
  • I downvoted this answer because the quote is about a very different serialization protocol (as @StephanTolksdorf found out) and thus rather misleading than helpful. – jcsahnwaldt Reinstate Monica Mar 11 '23 at 16:06
  • Apple makes a [distinction](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Archiving/Articles/objectgraphs.html) between "coding" (also called "archiving") on the one hand and "serialization" on the other hand. The question is about "coding", but this answer only applies to "serialization". See e.g. Apple's documentation about [Serializing Property Lists](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Archiving/Articles/serializing.html), which is **not** about "coding". – jcsahnwaldt Reinstate Monica Mar 11 '23 at 16:32
  • This answer is wrong, the other answers are correct. Here's a quote that actually applies to the question: "An archive can store an arbitrarily complex object graph. The archive preserves the identity of every object in the graph and all the relationships it has with all the other objects in the graph. When unarchived, the rebuilt object graph should, with few exceptions, be an exact copy of the original object graph." [Source](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Archiving/Articles/objectgraphs.html) – jcsahnwaldt Reinstate Monica Mar 11 '23 at 16:46