Nonlinearsound
Nonlinearsound

Reputation: 928

Nested NSDictionary Structure - How to hold a reference to the parent of every node in the tree

Using NSJSONSerialization on a returned NSData from a network call I get back a nested structure of NSDictionaries and NSArrays.

Now I wanted to parse that tree structure and prepare it for further use. Each node of the tree always carries an NSArray of sub nodes (NSDictionaries). Every one of these nodes should have a back reference to it's parent node, containing the NSArray the sub node is part of.

This is a basic example of the structure I am talking about:

Node {
 nodes:[
  node {parent:Node,name:foo},
  node {parent:Node,name:bar},
  node {parent:Node,name:baz},
 ]
,name:root}

Each node is an NSDictionary and each sub nodes collection an NSArray, containing NSDictionaries.

I learned, that I cannot just add a new key "parent" and set its value to the parent node dictionary. That creates a segfault when calling the object.

Basic example of the code, creating the parent key:

NSMutableDictionary * foo = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"foo",@"name",[NSNumber numberWithInt:1],@"value",nil];
NSMutableDictionary * bar = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"bar",@"name",[NSNumber numberWithInt:2],@"value",nil];
NSMutableDictionary * baz = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"baz",@"name",[NSNumber numberWithInt:3],@"value",nil];

NSMutableArray *array = [NSMutableArray arrayWithObjects:foo,bar,baz,nil];

NSMutableDictionary * container = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"root",@"name",array,@"nodes",nil];

[foo setValue:container forKey:@"parent"];

NSLog(@"%@",foo);  // <-- segfault here

Why am I getting a segmentation fault? Is this an infinite loop while printing out the description of the structure because of the back reference in the parent key of the node?

Dou you guys have any other approach to this problem here? Do I have to hold an external representaion of the tree structure, pointing to each key or IS there actually a way of storing some kind of reference to the parent node??

Many, many thanks in advance!!!

Upvotes: 0

Views: 2564

Answers (3)

charshep
charshep

Reputation: 416

I got around the limitation that prevents nested NSDictionaries from referring back to containing NSDictionaries (cyclic graphs) by wrapping the nested dictionaries inside instances of NSProxy:

@interface MyObjectProxy : NSProxy {
    id proxied;
}

-(id)initWithObject:(id)obj;

@end

@implementation MyObjectProxy

-(id)initWithObject:(id)obj
{
    proxied = obj;
    return self;
}

-(void)forwardInvocation:(NSInvocation*)invocation
{
    [invocation invokeWithTarget:proxied];
}

-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    return [[proxied class] instanceMethodSignatureForSelector:selector];
}

-(NSString*)descriptionWithLocale:(id)locale indent:(NSUInteger)level
{
    return [[self class] description];
}

@end

If I understand NSProxy correctly, this intercepts retain messages which in turn prevents the retain cycles that occur when nested dictionaries containing back references are added directly. At any rate, I know my proxies are being dealloced.

There are three minor problems with this that I've found. The first is that NSDictionary:descriptionWithLocale:indent: walks its entire structure, which causes the infinite loop already pointed out. The good news is that "overriding" it in NSProxy bypasses the sticky situation normally encountered whenever you try to override a class that's part of a class cluster (as NSDictionary is).

The second problem is that calling MyObjectProxy:class returns the MyObjectProxy Class rather than the NSDictionary Class. It's not a problem in my case but it's something to be aware of.

The third problem is that removing a child dictionary from a parent dictionary and deallocing the parent causes the corresponding MyObjectProxy:proxied to become a zombie, since MyObjectProxy can't retain the reference without reintroducing the retain cycle. Again, it's not a problem in my case since I never remove individual nodes from my structure but ensuring that you treat the entire structure as immutable is a limitation to be aware of.

Upvotes: 0

MadhavanRP
MadhavanRP

Reputation: 2830

It seems to me that you can use a simple objective C class here with the interface like

@interface Node : NSObject {
    Node              *parent;
    NSMutableArray    *nodes;
    NSString          *name
}
@end

I am not sure if this is the best way to do it, but you should not be using NSDictionary. The reason why you are getting the segmentation fault is probably because of the back reference creating an infinite loop in the NSLog.

EDIT: Upon googling, I found that there is a class NSTreeNode which should make things simpler for you.

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSTreeNode_class/Introduction/Introduction.html

Upvotes: 1

Andrew Madsen
Andrew Madsen

Reputation: 21383

The segfault on the NSLog line is due to an infinite loop. You could break that by subclassing NSMutableDictionary and overriding -description to specifically exclude printing the value for the key "parent". More generally though, NSMutableDictionary isn't designed to include its own container. For one thing, NSDictionary retains its child objects, so container retains foo and foo retains container, which creates a retain cycle.

My approach would be to write your own model classes. You can use an NSMutableDictionary object to store child nodes, with a weak @property/ivar holding a reference to the parent node.

Upvotes: 2

Related Questions