views:

450

answers:

6

I have a managed object ("A") that contains various attributes and types of relationships, and its relationships also have their own attributes & relationships. What I would like to do is to "copy" or "duplicate" the entire object graph rooted at object "A", and thus creating a new object "B" that is very similar to "A".

To be more specific, none of the relationships contained by "B" (or its children) should point to objects related to "A". There should be an entirely new object graph with similar relationships intact, and all objects having the same attributes, but of course different id's.

There is the obvious manual way to do this, but I was hoping to learn of a simpler means of doing so which was not totally apparent from the Core Data documentation.

TIA!

+1  A: 

This is called a "deep copy." Because it can be surprisingly expensive, a lot of languages/libraries don't support it out of the box and require you to roll your own. Cocoa is unfortunately one of them.

Chuck
Yes. "...surprisingly expensive..." as in unbounded memory usage. There's no way for the library to do this automatically without letting you easily shoot yourself in the foot.
Barry Wark
A: 

Something like this? (untested) This would be the “manual way” you mention, but it would automatically be in syncwith model changes and such so you wouldn't have to manually enter all the attribute names.

@interface MyObject (Clone)
- (MyObject *)clone;
@end

@implementation MyObject (Clone)

- (MyObject *)clone{

    MyObject *cloned = [NSEntityDescription
    insertNewObjectForEntityForName:@"MyObject"
    inManagedObjectContext:moc];

    NSDictionary *attributes = [[NSEntityDescription
    entityForName:@"MyObject"
    inManagedObjectContext:moc] attributesByName];

    for (NSString *attr in attributes) {
        [cloned setValue:[self valueForKey:attr] forKey:attr];
    }

    return cloned;
}

@end

This will return you a clone with all attributes and no relations copied over.

Jaanus
This doesn't do a "deep copy" of relationships that the OP wants.
Barry Wark
Yep, this only copies attributes. Could also do propertiesByName and/or relationshipsByName, but I won't add them to my example because (unlike the above) I have not done this kind of relation copying myself and can't vouch for it.
Jaanus
+1  A: 

What you are asking for is called a "deep copy". Because it can be very expensive (as in unbounded memory usage) and very difficult to get right (consider loops in the object graph), Core Data does not provide this facility for you.

There is often an architecture that avoids the need however. Instead of making a copy of an entire object graph, perhaps you can create a new entity that encapsulates the differences (or future differences) that you would have if you copied the object graph and then references the original graph only. In other words, instantiate a new "customizer" entity and don't copy the entire object graph. For example, consider a set of row houses. Each has identical framing and appliances, but the owner can customize the paint and furniture. Instead of deep copying the entire house graph for each owner, have a "painting and furniture" entity—that references the owner and the house model—for each owner.

Barry Wark
+1  A: 

Here's a class I created to perform a "deep copy" of managed objects: attributes and relationships. Note that this does not check against loops in the object graph. (Thanks Jaanus for the starting off point...)

@interface ManagedObjectCloner : NSObject {
}
+(NSManagedObject *)clone:(NSManagedObject *)source inContext:(NSManagedObjectContext *)context;
@end

@implementation ManagedObjectCloner

+(NSManagedObject *) clone:(NSManagedObject *)source inContext:(NSManagedObjectContext *)context{
    NSString *entityName = [[source entity] name];

    //create new object in data store
    NSManagedObject *cloned = [NSEntityDescription
                               insertNewObjectForEntityForName:entityName
                               inManagedObjectContext:context];

    //loop through all attributes and assign then to the clone
    NSDictionary *attributes = [[NSEntityDescription
                                 entityForName:entityName
                                 inManagedObjectContext:context] attributesByName];

    for (NSString *attr in attributes) {
        [cloned setValue:[source valueForKey:attr] forKey:attr];
    }

    //Loop through all relationships, and clone them.
    NSDictionary *relationships = [[NSEntityDescription
                                   entityForName:entityName
                                   inManagedObjectContext:context] relationshipsByName];
    for (NSRelationshipDescription *rel in relationships){
        NSString *keyName = [NSString stringWithFormat:@"%@",rel];
        //get a set of all objects in the relationship
        NSMutableSet *sourceSet = [source mutableSetValueForKey:keyName];
        NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName];
        NSEnumerator *e = [sourceSet objectEnumerator];
        NSManagedObject *relatedObject;
        while ( relatedObject = [e nextObject]){
            //Clone it, and add clone to set
            NSManagedObject *clonedRelatedObject = [ManagedObjectCloner clone:relatedObject 
                                                          inContext:context];
            [clonedSet addObject:clonedRelatedObject];
        }

    }

    return cloned;
}


@end
This is terrific! My only snag: UITableView does not always animate in the (effectively new) cell properly. I wonder if it concerns insertNewObjectForEntityForName:inManagedObjectContext:, THEN performing a deep copy, which may instigate more NSFetchedResultsControllerDelegate messages (?). I don't have any loops, and the data I'm copying isn't very deep per se, so hopefully that's good. Perhaps there's some way for me to somehow "build up" the entire cloned object, and THEN insert it in one fell swoop, or at least defer notification that an add took place. Looking to see if that's doable.
Joe D'Andrea
A: 

also:

[clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[properties allKeys]]]; [clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[attributes allKeys]]];

mixage
A: 

No. From visual interface! cmd c blabla property cmd v

Andrew E
This is wrong. They are asking how to perform a copy of the data within their object graph, not how to copy attributes within the data model in Xcode.
Brad Larson