2

Say I have this:

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@1] = @2;
dict[@3] = dict;

I archive dict by calling:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dict];

then I unarchive later:

NSMutableDictionary *dict2 = [NSKeyedUnarchiver unarchiveObjectWithData:data]

The problem is that dict2[@3] is not dict2, but rather an uninitialized NSDictionary, and I was not able to recover what I had put in. Does anyone know how I would work around this? Thanks in advance!

bogardon
  • 896
  • 2
  • 10
  • 22
  • 1
    Don't make self-referencing dictionaries. – Kreiri Jul 29 '13 at 06:46
  • To complete @Kreiri's comment: this runs afoul of memory management rules. Dictionaries use strong references, so by storing a dictionary inside itself, you create a leak. – zneak Jul 29 '13 at 07:52

2 Answers2

4

It is explained by an Apple engineer on the Apple Mailing List: http://lists.apple.com/archives/cocoa-dev/2007/May/msg00747.html, in reply to a similar question about archiving an NSMutableArray containing itself as an element.

Summary:

This (very probably -- I haven't looked into it) is not a problem with the recursion per-se, but rather with objects which replace themselves during unarchiving by returning a new object from initWithCoder:.
...
So the summary answer is: you can't reliably have recursive references. In practice, though, these do occur (think of the NSView subview/superview relationship) and things squeak by. Sometimes they don't and it's impossible to know beforehand if something will work or not.

Chris Kane
Cocoa Frameworks, Apple

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

I would be really surprised if this worked. When you set dict[@3]=dict, you are basically giving it an infinite loop to create an infinitely deep dictionary. When you create data from the dictionary, it seems to protect you from that by replacing the infinite dictionary with an uninitialized one so that the user's ram is not completely drained before the application crashes.

If you were to try to print out dict[@3] in the console, the application would infinitely loop trying to print it out and would get stuck until it finally crashes sometime later.

Hence, f you want to store your dictionary in a dictionary, create another one.

Jsdodgers
  • 5,253
  • 2
  • 20
  • 36
  • I, on the other hand, am rather surprised that it _doesn't_ work. Java serialization gets recursive references right, and the .NET equivalent probably does as well. I'd rather say that it uses an uninitialized value as a result of laziness/shakiness, not in order to protect you from consuming all the memory. I can understand why a program would crash if it found a recursive reference, but I also know how I could avoid that kind of crash and I find it disappointing that framework code won't do it. – zneak Jul 29 '13 at 07:50
  • I don't understand what you are trying to say. It doesn't crash if you make a recursive reference, it crashes if you try to print it out because it would infinitely be printing. Also, it does not add the recursive reference to the data because then you would have an infinitely lengthed data that would flood the ram. – Jsdodgers Jul 29 '13 at 07:53
  • fwiw, lldb prints `[no Objective-C description available]` – bogardon Jul 29 '13 at 08:01
  • I'm trying to say that these uninitialized values are a bug, not a feature. In my book, getting incorrect results from a function is in no way better than a crash. There are very decent serialization protocols that work around recursive references and correctly unserialize, and there are very decent ways to print an object and make sure you don't enter a cycle. The fact that the naive approach doesn't work doesn't mean that the problem is insurmountable (or even just hard), and I'd expect that a multi-billion dollar company would get that right. – zneak Jul 29 '13 at 08:02