Alex Rivera | Logout

How can I duplicate, or copy a Core Data Managed Object?

Asked 2010-04-28T15:22:57.463
68

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!

Edit
Report

2 Answers

43

These answers got me really close, though they did seem to have some shortcomings:

1st, I took the advice of Z S and made it a category on NSManagedObject, this seemed a little cleaner to me.

2nd, My object graph contains to-one relationships, so I started from levous's example, but note that levous's example isn't cloning the object in the case of the to-one relationship. This will cause a crash (attempting to save a NSMO from one context in a different context). I have addressed that in the example below.

3rd, I provided a cache of already-cloned objects, this prevents objects from being cloned twice and therefore duplicated in the new object graph, and also prevents cycles.

4th, I've added a black-list (list of Entity-types not to clone). I did this in part to solve one shortcoming of my final solution, which I will describe below.

NOTE: if you use what I understand to a be CoreData best-practice, always providing inverse relationships, then this will likely clone all objects that have a relationship to the object you want to clone. If you are using inverses and you have a single root object that knows about all other objects, then you will likely clone the whole thing. My solution to this was to add the blacklist and pass in the Entity type that I knew was a parent of one of the objects I wanted cloned. This appears to work for me. :)

Happy cloning!

// NSManagedObject+Clone.h
#import <CoreData/CoreData.h>

@interface NSManagedObject (Clone)
- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude;
@end


// NSManagedObject+Clone.m
#import "NSManagedObject+Clone.h"

@implementation NSManagedObject (Clone)

- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSArray *)namesOfEntitiesToExclude {
  NSString *entityName = [[self entity] n
answered 2011-09-30T16:35:36.673
6

I have modified Derrick's answer, which worked perfectly for me, to support ordered relationships available in iOS 5.0 and Mac OS X 10.7:

//
//  NSManagedObject+Clone.h
//

#import <CoreData/CoreData.h>

#ifndef CD_CUSTOM_DEBUG_LOG
#define CD_CUSTOM_DEBUG_LOG NSLog
#endif

@interface NSManagedObject (Clone)

- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context
                    excludeEntities:(NSArray *)namesOfEntitiesToExclude;

@end

//
//  NSManagedObject+Clone.m
//

#import "NSManagedObject+Clone.h"

@interface NSManagedObject (ClonePrivate)
- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context
                    withCopiedCache:(NSMutableDictionary **)alreadyCopied
                    excludeEntities:(NSArray *)namesOfEntitiesToExclude;
@end

@implementation NSManagedObject (Clone)

- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context
                    withCopiedCache:(NSMutableDictionary **)alreadyCopied
                    excludeEntities:(NSArray *)namesOfEntitiesToExclude {
    if (!context) {
        CD_CUSTOM_DEBUG_LOG(@"%@:%@ Try to clone NSManagedObject in the 'nil' context.",
                            THIS_CLASS,
                            THIS_METHOD);
        return nil;
    }
    NSString *entityName = [[self entity] name];

    if ([namesOfEntitiesToExclude containsObject:entityName]) {
        return nil;
    }
    NSManagedObject *cloned = nil;
    if (alreadyCopied != NULL) {
        cloned = [*alreadyCopied objectForKey:[self objectID]];
        if (cloned) {
            return cloned;
        }
        // Create new object in data store
        cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName
                    
answered 2012-03-03T12:42:01.347

Your Answer