views:

371

answers:

2

I have the following code which seems to go on indefinitely until the app crashes. It seems to happen with the recursion in the datastructureFromManagedObject method. I suspect that this method:

1) looks at the first managed object and follows any relationship property recursively. 2) examines the object at the other end of the relationship found at point 1 and repeats the process.

Is it possible that if managed object A has a to-many relationship with object B and that relationship is two-way (i.e an inverse to-one relationship to A from B - e.g. one department has many employees but each employee has only one department) that the following code gets stuck in infinite recursion as it follows the to-one relationship from object B back to object A and so on.

If so, can anyone provide a fix for this so that I can get my whole object graph of managed objects converted to JSON.

#import "JSONUtils.h"


@implementation JSONUtils

- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject *)managedObject {

    NSDictionary *attributesByName = [[managedObject entity] attributesByName];
    NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];

    //getting the values correspoinding to the attributes collected in attributesByName
    NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];

    //sets the name for the entity being encoded to JSON
    [valuesDictionary setObject:[[managedObject entity] name] forKey:@"ManagedObjectName"];

    NSLog(@"+++++++++++++++++> before the for loop");
    //looks at each relationship for the given managed object
    for (NSString *relationshipName in [relationshipsByName allKeys]) {
        NSLog(@"The relationship name = %@",relationshipName);
        NSRelationshipDescription *description = [relationshipsByName objectForKey:relationshipName];

        if (![description isToMany]) {
            NSLog(@"The relationship is NOT TO MANY!");
            [valuesDictionary setObject:[self dataStructureFromManagedObject:[managedObject valueForKey:relationshipName]] forKey:relationshipName];
            continue;
        }
        NSSet *relationshipObjects = [managedObject valueForKey:relationshipName];
        NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
        for (NSManagedObject *relationshipObject in relationshipObjects) {
            [relationshipArray addObject:[self dataStructureFromManagedObject:relationshipObject]];
        }
        [valuesDictionary setObject:relationshipArray forKey:relationshipName];
    }
    return [valuesDictionary autorelease];
}

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {

    NSMutableArray *dataArray = [[NSArray alloc] init];
    for (NSManagedObject *managedObject in managedObjects) {
        [dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
    }
    return [dataArray autorelease];
}


//method to call for obtaining JSON structure - i.e. public interface to this class
- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects {
    NSLog(@"-------------> just before running the recursive method");
    NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
    NSLog(@"-------------> just before running the serialiser");
    NSString *jsonString = [[CJSONSerializer serializer] serializeArray:objectsArray];
    return jsonString;
}

- (NSManagedObject*)managedObjectFromStructure:(NSDictionary*)structureDictionary withManagedObjectContext:(NSManagedObjectContext*)moc {

    NSString *objectName = [structureDictionary objectForKey:@"ManagedObjectName"];
    NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:objectName inManagedObjectContext:moc];
    [managedObject setValuesForKeysWithDictionary:structureDictionary];

    for (NSString *relationshipName in [[[managedObject entity] relationshipsByName] allKeys]) {
        NSRelationshipDescription *description = [[[managedObject entity]relationshipsByName] objectForKey:relationshipName];
        if (![description isToMany]) {
            NSDictionary *childStructureDictionary = [structureDictionary objectForKey:relationshipName];
            NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
            [managedObject setValue:childObject forKey:relationshipName];
            continue;
        }
        NSMutableSet *relationshipSet = [managedObject mutableSetValueForKey:relationshipName];
        NSArray *relationshipArray = [structureDictionary objectForKey:relationshipName];
        for (NSDictionary *childStructureDictionary in relationshipArray) {
            NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
            [relationshipSet addObject:childObject];
        }
    }
    return managedObject;
}

//method to call for obtaining managed objects from JSON structure - i.e. public interface to this class
- (NSArray*)managedObjectsFromJSONStructure:(NSString *)json withManagedObjectContext:(NSManagedObjectContext*)moc {

    NSError *error = nil;
    NSArray *structureArray = [[CJSONDeserializer deserializer] 
                               deserializeAsArray:[json dataUsingEncoding:NSUTF32BigEndianStringEncoding] 
                               error:&error];

    NSAssert2(error == nil, @"Failed to deserialize\n%@\n%@", [error localizedDescription], json);
    NSMutableArray *objectArray = [[NSMutableArray alloc] init];

    for (NSDictionary *structureDictionary in structureArray) {
        [objectArray addObject:[self managedObjectFromStructure:structureDictionary withManagedObjectContext:moc]];
    }
    return [objectArray autorelease];
}


@end
+1  A: 

I answered this question when you posted a comment on the original thread. You need to make some changes to how the recursion works so that it doesn't go into a loop. There are many ways to do this.

For example, you can change the call to get all relationships to instead call a method in your NSManagedObject subclasses that only returns the relationships that are downstream. In that design ObjectA would return the ObjectB relationship but Object B would not return any (or relationships to ObjectC, etc.). This creates a tree like hierarchy for the recursion to work through.

Follow the logic of the code. It process the object or objects you hand to it and then it walks through every object associated with that first set of objects. You already, from your post, showed that you understand it is a loop. Now you need to break that loop in your code with logic to change it from a loop to a tree.

Also, I realize this may sound like I am pimping my book, I explained how to do avoid this loop in my book in the Multi-threading chapter in the section on exporting recipes.

Update NSDate

That sounds like a bug in the JSON parser that you are using as it should be able to handle dates. However your workaround is viable except you need to convert it on both sides which is a PITA. I would look into your parser and see why it is not translating dates correctly as that is a pretty big omission.

Marcus S. Zarra
Thanks for your response. I think that I've managed to solve the endless recursion problem but the code now throws an exception which seems to suggest that NSDate attributes cannot be serialised (i.e. Terminating app due to uncaught exception 'NSGenericException', reason: 'Cannot serialize data of type '__NSCFDate'') Is this the case? If so, is there any way around this?I have bought your book and will endeavour to read it in greater detail. Thanks again for your help thus far. I'm an Objective-C beginner and am trying to make some headway in this area.
Urizen
I've got round the above-mentioned problem by storing the value returned by [date timeIntervalSince1970] as an NSNumber. Will have a look at the section you mention in your book.
Urizen
A: 

I just wanted to point out a small typo, that caused the code to crash, and hopefully this will save you a few min.

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {

    NSMutableArray *dataArray = [[NSArray alloc] init];
    for (NSManagedObject *managedObject in managedObjects) {
        [dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
    }
    return [dataArray autorelease];
}

The NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray

really should be NSMutableArray *dataArray = [[NSMutableArray alloc] init];

that is all.

thank you

iAm