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

CocoaCore DataNsmanagedobject

Cocoa Problem Overview


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!

Cocoa Solutions


Solution 1 - Cocoa

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

Solution 2 - Cocoa

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] name];
  
  if ([namesOfEntitiesToExclude containsObject:entityName]) {
    return nil;
  }
  
  NSManagedObject *cloned = [alreadyCopied objectForKey:[self objectID]];
  if (cloned != nil) {
    return cloned;
  }
  
  //create new object in data store
  cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
  [alreadyCopied setObject:cloned forKey:[self objectID]];
  
  //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:[self valueForKey:attr] forKey:attr];
  }
  
  //Loop through all relationships, and clone them.
  NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName];
  for (NSString *relName in [relationships allKeys]){
    NSRelationshipDescription *rel = [relationships objectForKey:relName];
    
    NSString *keyName = rel.name;
    if ([rel isToMany]) {
      //get a set of all objects in the relationship
      NSMutableSet *sourceSet = [self 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 = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude];
        [clonedSet addObject:clonedRelatedObject];
      }
    }else {
      NSManagedObject *relatedObject = [self valueForKey:keyName];
      if (relatedObject != nil) {
        NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude];
        [cloned setValue:clonedRelatedObject forKey:keyName];
      }
    }
  }
  
  return cloned;
}

- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude {
  return [self cloneInContext:context withCopiedCache:[NSMutableDictionary dictionary] exludeEntities:namesOfEntitiesToExclude];
}

@end

Solution 3 - Cocoa

I've updated user353759's answer to support toOne relationships.

@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 (NSString *relName in [relationships allKeys]){
		NSRelationshipDescription *rel = [relationships objectForKey:relName];
		
        NSString *keyName = [NSString stringWithFormat:@"%@",rel];
		if ([rel isToMany]) {
			//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];
			}
		}else {
			[cloned setValue:[source valueForKey:keyName] forKey:keyName];
		}
		
    }
	
    return cloned;
}

Solution 4 - Cocoa

This is @Derricks answer, modified to support the new-as-of iOS 6.0 ordered to-many relationships by interrogating the relationship to see if it is ordered. While I was there, I added a simpler -clone method for the common case of cloning within the same NSManagedObjectContext.

//
//  NSManagedObject+Clone.h
//  Tone Poet
//
//  Created by Mason Kramer on 5/31/13.
//  Copyright (c) 2013 Mason Kramer. The contents of this file are available for use by anyone, for any purpose whatsoever.
//

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface NSManagedObject (Clone) {
}

-(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSArray *)namesOfEntitiesToExclude;
-(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude;
-(NSManagedObject *) clone;

@end

//
//  NSManagedObject+Clone.m
//  Tone Poet
//
//  Created by Mason Kramer on 5/31/13.
//  Copyright (c) 2013 Mason Kramer. The contents of this file are available for use by anyone, for any purpose whatsoever.
//


#import "NSManagedObject+Clone.h"

@implementation NSManagedObject (Clone)

-(NSManagedObject *) clone {
    return [self cloneInContext:[self managedObjectContext] exludeEntities:@[]];
}

- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSArray *)namesOfEntitiesToExclude {
    NSString *entityName = [[self entity] name];
    
    if ([namesOfEntitiesToExclude containsObject:entityName]) {
        return nil;
    }
    
    NSManagedObject *cloned = [alreadyCopied objectForKey:[self objectID]];
    if (cloned != nil) {
        return cloned;
    }
    
    //create new object in data store
    cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
    [alreadyCopied setObject:cloned forKey:[self objectID]];
    
    //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:[self valueForKey:attr] forKey:attr];
    }
    
    //Loop through all relationships, and clone them.
    NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName];
    for (NSString *relName in [relationships allKeys]){
        NSRelationshipDescription *rel = [relationships objectForKey:relName];
        
        NSString *keyName = rel.name;
        if ([rel isToMany]) {
            if ([rel isOrdered]) {
                NSMutableOrderedSet *sourceSet = [self mutableOrderedSetValueForKey:keyName];
                NSMutableOrderedSet *clonedSet = [cloned mutableOrderedSetValueForKey:keyName];
                
                NSEnumerator *e = [sourceSet objectEnumerator];
                
                NSManagedObject *relatedObject;
                while ( relatedObject = [e nextObject]){
                    //Clone it, and add clone to set
                    NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude];
                    
                    
                    [clonedSet addObject:clonedRelatedObject];
                    [clonedSet addObject:clonedRelatedObject];
                }
            }
            else {
                NSMutableSet *sourceSet = [self 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 = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude];

                    [clonedSet addObject:clonedRelatedObject];
                }
            }
        }
        else {
            NSManagedObject *relatedObject = [self valueForKey:keyName];
            if (relatedObject != nil) {
                NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude];
                [cloned setValue:clonedRelatedObject forKey:keyName];
            }
        }

    }
    
    return cloned;
}

-(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude {
    return [self cloneInContext:context withCopiedCache:[NSMutableDictionary dictionary] exludeEntities:namesOfEntitiesToExclude];
}
@end

Solution 5 - Cocoa

Swift 5

This builds upon @Derrick & @Dmitry Makarenko & @masonk's contributions, bringing everything together, improving on it and turning it into a solution fit for 2020.

  • Handles both 1-to-1 & 1-to-Many realtionships
  • Handles ordered relationships
  • Copies the entire NSManagedObject graph (using an alreadyCopied cache)
  • Implemented as an extension to NSManagedObject

.

import CoreData

extension NSManagedObject {

    func copyEntireObjectGraph(context: NSManagedObjectContext) -> NSManagedObject {
    
        var cache = Dictionary<NSManagedObjectID, NSManagedObject>()
        return cloneObject(context: context, cache: &cache)
    
    }

    func cloneObject(context: NSManagedObjectContext, cache alreadyCopied: inout Dictionary<NSManagedObjectID, NSManagedObject>) -> NSManagedObject {
    
        guard let entityName = self.entity.name else {
            fatalError("source.entity.name == nil")
        }
    
        if let storedCopy = alreadyCopied[self.objectID] {
            return storedCopy
        }
    
        let cloned = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context)
    alreadyCopied[self.objectID] = cloned

        if let attributes = NSEntityDescription.entity(forEntityName: entityName, in: context)?.attributesByName {

            for key in attributes.keys {
                cloned.setValue(self.value(forKey: key), forKey: key)
            }
        
        }

        if let relationships = NSEntityDescription.entity(forEntityName: entityName, in: context)?.relationshipsByName {
    
            for (key, value) in relationships {
            
                if value.isToMany {

                    if let sourceSet = self.value(forKey: key) as? NSMutableOrderedSet {
                
                        guard let clonedSet = cloned.value(forKey: key) as? NSMutableOrderedSet else {
                            fatalError("Could not cast relationship \(key) to an NSMutableOrderedSet")
                        }
                    
                        let enumerator = sourceSet.objectEnumerator()

                        var nextObject = enumerator.nextObject() as? NSManagedObject

                        while let relatedObject = nextObject {
                        
                            let clonedRelatedObject = relatedObject.cloneObject(context: context, cache: &alreadyCopied)
                            clonedSet.add(clonedRelatedObject)
                            nextObject = enumerator.nextObject() as? NSManagedObject
                        
                        }
                    
                    } else if let sourceSet = self.value(forKey: key) as? NSMutableSet {
                
                        guard let clonedSet = cloned.value(forKey: key) as? NSMutableSet else {
                            fatalError("Could not cast relationship \(key) to an NSMutableSet")
                        }
                    
                        let enumerator = sourceSet.objectEnumerator()

                        var nextObject = enumerator.nextObject() as? NSManagedObject

                        while let relatedObject = nextObject {
                        
                            let clonedRelatedObject = relatedObject.cloneObject(context: context, cache: &alreadyCopied)
                            clonedSet.add(clonedRelatedObject)
                            nextObject = enumerator.nextObject() as? NSManagedObject
                        
                        }
                    
                    }
            
                } else {
                
                    if let relatedObject = self.value(forKey: key) as? NSManagedObject {
                
                        let clonedRelatedObject = relatedObject.cloneObject(context: context, cache: &alreadyCopied)
                        cloned.setValue(clonedRelatedObject, forKey: key)
                    
                    }
                
                }
            
            }
        
        }

        return cloned
    
    }

}

Usage:

let myManagedObjectCopy = myManagedObject.copyEntireObjectGraph(context: myContext)

Solution 6 - Cocoa

I've noticed a couple of bugs with the current answers. Firstly, something seems to be mutating the set of to-Many related objects as its being iterated over. Secondly, I'm not sure if something has changed within the API, but using the NSRelationshipDescription's String representation as a key was throwing exceptions when grabbing those related objects.

I've made a few tweaks, have done some basic testing and it seems to work. If anyone wants to investigate further that would be great!

@implementation NSManagedObjectContext (DeepCopy)

-(NSManagedObject *) clone:(NSManagedObject *)source{
    NSString *entityName = [[source entity] name];
    
    //create new object in data store
    NSManagedObject *cloned = [NSEntityDescription
                               insertNewObjectForEntityForName:entityName
                               inManagedObjectContext:self];
    
    //loop through all attributes and assign then to the clone
    NSDictionary *attributes = [[NSEntityDescription
                                 entityForName:entityName
                                 inManagedObjectContext:self] 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:self] relationshipsByName];
    
    for (NSString *relName in [relationships allKeys]){
        
        NSRelationshipDescription *rel = [relationships objectForKey:relName];
        if ([rel isToMany]) {
            //get a set of all objects in the relationship
            NSArray *sourceArray = [[source mutableSetValueForKey:relName] allObjects];
            NSMutableSet *clonedSet = [cloned mutableSetValueForKey:relName];
            for(NSManagedObject *relatedObject in sourceArray) {
                NSManagedObject *clonedRelatedObject = [self clone:relatedObject];
                [clonedSet addObject:clonedRelatedObject];
            }
        } else {
            [cloned setValue:[source valueForKey:relName] forKey:relName];
        }
        
    }
    
    return cloned;
}

@end

Solution 7 - Cocoa

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
                                               inManagedObjectContext:context];
        [*alreadyCopied setObject:cloned forKey:[self objectID]];
    } else {
        CD_CUSTOM_DEBUG_LOG(@"%@:%@ NULL pointer was passed in 'alreadyCopied' argument.",
                            THIS_CLASS,
                            THIS_METHOD);
    }
    // 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:[self valueForKey:attr] forKey:attr];
    }

    // Loop through all relationships, and clone them.
    NSDictionary *relationships = [[NSEntityDescription entityForName:entityName
                                               inManagedObjectContext:context] relationshipsByName];
    NSArray *relationshipKeys = [relationships allKeys];
    for (NSString *relName in relationshipKeys) {
        NSRelationshipDescription *rel = [relationships objectForKey:relName];
        NSString *keyName = [rel name];
        if ([rel isToMany]) {
            if ([rel isOrdered]) {
                // Get a set of all objects in the relationship
                NSMutableOrderedSet *sourceSet = [self mutableOrderedSetValueForKey:keyName];
                NSMutableOrderedSet *clonedSet = [cloned mutableOrderedSetValueForKey:keyName];
                for (id relatedObject in sourceSet) {
                    //Clone it, and add clone to set
                    NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context
                                                                         withCopiedCache:alreadyCopied
                                                                         excludeEntities:namesOfEntitiesToExclude];
                    if (clonedRelatedObject) {
                        [clonedSet addObject:clonedRelatedObject];
                    }
                }
            } else {
                // Get a set of all objects in the relationship
                NSMutableSet *sourceSet = [self mutableSetValueForKey:keyName];
                NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName];
                for (id relatedObject in sourceSet) {
                    //Clone it, and add clone to set
                    NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context
                                                                         withCopiedCache:alreadyCopied
                                                                         excludeEntities:namesOfEntitiesToExclude];
                    if (clonedRelatedObject) {
                        [clonedSet addObject:clonedRelatedObject];
                    }
                }
            }
        } else {
            NSManagedObject *relatedObject = [self valueForKey:keyName];
            if (relatedObject) {
                NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context
                                                                     withCopiedCache:alreadyCopied
                                                                     excludeEntities:namesOfEntitiesToExclude];
                [cloned setValue:clonedRelatedObject forKey:keyName];
            }
        }
    }
    return cloned;
}

- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context
                    excludeEntities:(NSArray *)namesOfEntitiesToExclude {
    NSMutableDictionary* mutableDictionary = [NSMutableDictionary dictionary];
    return [self cloneInContext:context
                withCopiedCache:&mutableDictionary
                excludeEntities:namesOfEntitiesToExclude];
}

@end

Solution 8 - Cocoa

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.

Swift 3:

extension NSManagedObject {
    func shallowCopy() -> NSManagedObject? {
        guard let context = managedObjectContext, let entityName = entity.name else { return nil }
        let copy = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context)
        let attributes = entity.attributesByName
        for (attrKey, _) in attributes {
            copy.setValue(value(forKey: attrKey), forKey: attrKey)
        }
        return copy
    }
}

Objective-C:

@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.

Solution 9 - Cocoa

I had a real need to work around the mass copying issue that @derrick acknowledged in his original answer. I modified from MasonK's version. This doesn't have a lot of the elegance that was in the previous versions; but it appears to solve a key issue (unintended duplicates of similar entities) in my application.

//
//  NSManagedObject+Clone.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface NSManagedObject (Clone) {
}

-(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude  isFirstPass:(BOOL)firstPass;

-(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude;

-(NSManagedObject *) clone;

@end

//
//  NSManagedObject+Clone.m
//

#import "NSManagedObject+Clone.h"

@implementation NSManagedObject (Clone)

-(NSManagedObject *) clone {
    NSMutableArray *emptyArray = [NSMutableArray arrayWithCapacity:1];
    return [self cloneInContext:[self managedObjectContext] exludeEntities:emptyArray];
}


- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude
isFirstPass:(BOOL)firstPass
{
    NSString *entityName = [[self entity] name];
    
    if ([namesOfEntitiesToExclude containsObject:entityName]) {
        return nil;
    }
    
    NSManagedObject *cloned = [alreadyCopied objectForKey:[self objectID]];
    if (cloned != nil) {
        return cloned;
    }
    
    //create new object in data store
    cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
    [alreadyCopied setObject:cloned forKey:[self objectID]];
    
    //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:[self valueForKey:attr] forKey:attr];
    }
    
    //Inverse relationships can cause all of the entities under one area to get duplicated
    //This is the reason for "isFirstPass" and "excludeEntities"
    
    if (firstPass == TRUE) {
        [namesOfEntitiesToExclude addObject:entityName];
        firstPass=FALSE;
    }
    
    //Loop through all relationships, and clone them.
    NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName];
    for (NSString *relName in [relationships allKeys]){
        NSRelationshipDescription *rel = [relationships objectForKey:relName];
        
        NSString *keyName = rel.name;
        if ([rel isToMany]) {
            if ([rel isOrdered]) {
                NSMutableOrderedSet *sourceSet = [self mutableOrderedSetValueForKey:keyName];
                NSMutableOrderedSet *clonedSet = [cloned mutableOrderedSetValueForKey:keyName];
                
                NSEnumerator *e = [sourceSet objectEnumerator];
                
                NSManagedObject *relatedObject;
                while ( relatedObject = [e nextObject]){
                    //Clone it, and add clone to set
                    NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude
                                                            isFirstPass:firstPass];
                    
                    if (clonedRelatedObject != nil) {
                    [clonedSet addObject:clonedRelatedObject];
                    [clonedSet addObject:clonedRelatedObject];
                    }
                }
            }
            else {
                NSMutableSet *sourceSet = [self 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 = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude
                                                                             isFirstPass:firstPass];
                    if (clonedRelatedObject != nil) {
                    [clonedSet addObject:clonedRelatedObject];
                    }
                }
            }
        }
        else {
            NSManagedObject *relatedObject = [self valueForKey:keyName];
            if (relatedObject != nil) {
                NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude
                isFirstPass:firstPass];
                if (clonedRelatedObject != nil) {
                [cloned setValue:clonedRelatedObject forKey:keyName];
                }
            }
        }
        
    }
    
    return cloned;
}

-(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude {
    return [self cloneInContext:context withCopiedCache:[NSMutableDictionary dictionary] exludeEntities:namesOfEntitiesToExclude isFirstPass:TRUE];
}
@end

Solution 10 - Cocoa

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.

Solution 11 - Cocoa

Here you have my swift 3 approach:

func shallowCopy(copyRelations: Bool) -> NSManagedObject? {
        
        guard let context = managedObjectContext, let entityName = entity.name else { return nil }
        let copy = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context)
        let attributes = entity.attributesByName
        for (attrKey, _) in attributes {
            copy.setValue(value(forKey: attrKey), forKey: attrKey)
        }
        
        if copyRelations {
            let relations = entity.relationshipsByName
            
            for (relKey, relValue) in relations {
                if relValue.isToMany {
                    let sourceSet = mutableSetValue(forKey: relKey)
                    let clonedSet = copy.mutableSetValue(forKey: relKey)
                    let enumerator = sourceSet.objectEnumerator()
                    
                    while let relatedObject = enumerator.nextObject()  {
                        let clonedRelatedObject = (relatedObject as! NSManagedObject).shallowCopy(copyRelations: false)
                        clonedSet.add(clonedRelatedObject!)
                    }
                } else {
                    copy.setValue(value(forKey: relKey), forKey: relKey)
                }
            }
        }
        
        return copy
    }

Solution 12 - Cocoa

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.

Solution 13 - Cocoa

Also:

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

Solution 14 - Cocoa

If you want to only relate Entities in the Relationship Hierarchie you only have to add the following Code to the solution of Dmitry

Between this

NSString *entityName = [[self entity] name];

HERE if ([namesOfEntitiesToExclude containsObject:entityName]) {

NSMutableArray *arrayToOnlyRelate   = [NSMutableArray arrayWithObjects:@"ENTITY 1",@"ENTITY 2",@"ENTITY 3", nil];

if ([arrayToOnlyRelate containsObject:entityName]) {
    return self;
}

Solution 15 - Cocoa

My take on this is at https://gist.github.com/jpmhouston/7958fceae9216f69178d4719a3492577

  • passes rel.inverseRelationship.name into the recursive method to omit visiting the inverse relationships rather than maintaining a set of alreadyCopied objects

  • shallow or deep copies

  • accepts keypaths of relationships to not clone, but to either omit, or to simply copy if the inverse is a to-many relationship

  • workaround for ordered,to-many relationships ending up in backwards order - simply iterate over the source entities backwards :) i'm not sure if this a good idea or if it even works all the time

Feedback & comments welcome, especially if someone can elaborate on Benjohn's comment on mis-ordering above "The work around for this is to build the complete ordered set and then assign using the primitive KVO variant." and can improve my ordered,to-many workaround.

Also, I'm using MagicalRecord and so my code assumes that, including providing easy methods that use its default context.

Solution 16 - Cocoa

Swift 4.0 version

import UIKit
import CoreData

class ManagedObjectCloner: NSObject {

    static func cloneObject(source :NSManagedObject, context :NSManagedObjectContext) -> NSManagedObject{
        let entityName = source.entity.name
        let cloned = NSEntityDescription.insertNewObject(forEntityName: entityName!, into: context)
        
        let attributes = NSEntityDescription.entity(forEntityName: entityName!, in: context)?.attributesByName
        
        for (key,_) in attributes! {
            cloned.setValue(source.value(forKey: key), forKey: key)
        }
        
        let relationships = NSEntityDescription.entity(forEntityName: entityName!, in: context)?.relationshipsByName
        for (key,_) in relationships! {
            let sourceSet = source.mutableSetValue(forKey: key)
            let clonedSet = cloned.mutableSetValue(forKey: key)
            let e = sourceSet.objectEnumerator()
            
            var relatedObj = e.nextObject() as? NSManagedObject
            
            while ((relatedObj) != nil) {
                let clonedRelatedObject = ManagedObjectCloner.cloneObject(source: relatedObj!, context: context)
                clonedSet.add(clonedRelatedObject)
                relatedObj = e.nextObject() as? NSManagedObject
            }
        }
        
        return cloned
    }
    
}

Solution 17 - Cocoa

This is a slight improvement on the contributions of @Geoff H, @Derrick, @Dmitry Makarenko & @masonk. The code uses a more functional style and throws friendly errors .

Feature list:

  • Handles both One-to-One & One-to-Many relationships
  • Handles ordered relationships
  • Copies the entire NSManagedObject graph (using an alreadyCopied cache)
  • Implemented as an extension to NSManagedObject
  • NEW: Throws a custom DeepCopyError instead of crashing with fatalError()
  • NEW: Optionally retrieves the NSManagedObjectContext from the NSManagedObject itself

Swift 5.0 code

WARNING: this code is only partially tested!

import CoreData

extension NSManagedObject {
    
    enum DeepCopyError: Error {
        case missingContext
        case missingEntityName(NSManagedObject)
        case unmanagedObject(Any)
    }
    
    func deepcopy(context: NSManagedObjectContext? = nil) throws -> NSManagedObject {
        
        if let context = context ?? managedObjectContext {
            
            var cache = Dictionary<NSManagedObjectID, NSManagedObject>()
            return try deepcopy(context: context, cache: &cache)
            
        } else {
            throw DeepCopyError.missingContext
        }
    }

    private func deepcopy(context: NSManagedObjectContext, cache alreadyCopied: inout Dictionary<NSManagedObjectID, NSManagedObject>) throws -> NSManagedObject {
                
        guard let entityName = self.entity.name else {
            throw DeepCopyError.missingEntityName(self)
        }
        
        if let storedCopy = alreadyCopied[self.objectID] {
            return storedCopy
        }

        let cloned = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context)
        alreadyCopied[self.objectID] = cloned
        
        // Loop through all attributes and assign then to the clone
        NSEntityDescription
            .entity(forEntityName: entityName, in: context)?
            .attributesByName
            .forEach { attribute in
                cloned.setValue(value(forKey: attribute.key), forKey: attribute.key)
            }
        
        // Loop through all relationships, and clone them.
        try NSEntityDescription
            .entity(forEntityName: entityName, in: context)?
            .relationshipsByName
            .forEach { relation in
                
                if relation.value.isToMany {
                    if relation.value.isOrdered {
                        
                        // Get a set of all objects in the relationship
                        let sourceSet = mutableOrderedSetValue(forKey: relation.key)
                        let clonedSet = cloned.mutableOrderedSetValue(forKey: relation.key)
                        
                        for object in sourceSet.objectEnumerator() {
                            if let relatedObject = object as? NSManagedObject {
                                
                                // Clone it, and add clone to the set
                                let clonedRelatedObject = try relatedObject.deepcopy(context: context, cache: &alreadyCopied)
                                clonedSet.add(clonedRelatedObject as Any)
                                
                            } else {
                                throw DeepCopyError.unmanagedObject(object)
                            }
                        }
                        
                    } else {
                        
                        // Get a set of all objects in the relationship
                        let sourceSet = mutableSetValue(forKey: relation.key)
                        let clonedSet = cloned.mutableSetValue(forKey: relation.key)
                        
                        for object in sourceSet.objectEnumerator() {
                            if let relatedObject = object as? NSManagedObject {
                                
                                // Clone it, and add clone to the set
                                let clonedRelatedObject = try relatedObject.deepcopy(context: context, cache: &alreadyCopied)
                                clonedSet.add(clonedRelatedObject as Any)
                                
                            } else {
                                throw DeepCopyError.unmanagedObject(object)
                            }
                        }
                    }
                    
                } else if let relatedObject = self.value(forKey: relation.key) as? NSManagedObject {
                    
                    // Clone it, and assign then to the clone
                    let clonedRelatedObject = try relatedObject.deepcopy(context: context, cache: &alreadyCopied)
                    cloned.setValue(clonedRelatedObject, forKey: relation.key)
                    
                }
            }
        
        return cloned
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManiView Question on Stackoverflow
Solution 1 - Cocoauser353759View Answer on Stackoverflow
Solution 2 - CocoaDerrickView Answer on Stackoverflow
Solution 3 - CocoalevousView Answer on Stackoverflow
Solution 4 - CocoamasonkView Answer on Stackoverflow
Solution 5 - CocoaGeoff HView Answer on Stackoverflow
Solution 6 - CocoaNathan GaskinView Answer on Stackoverflow
Solution 7 - CocoaDmitry MakarenkoView Answer on Stackoverflow
Solution 8 - CocoaJaanusView Answer on Stackoverflow
Solution 9 - CocoaJustinThibaultView Answer on Stackoverflow
Solution 10 - CocoaBarry WarkView Answer on Stackoverflow
Solution 11 - CocoaBiscaView Answer on Stackoverflow
Solution 12 - CocoaChuckView Answer on Stackoverflow
Solution 13 - CocoamixageView Answer on Stackoverflow
Solution 14 - CocoaKurt57View Answer on Stackoverflow
Solution 15 - CocoaPierre HoustonView Answer on Stackoverflow
Solution 16 - CocoaAaban Tariq MurtazaView Answer on Stackoverflow
Solution 17 - CocoaAlexander BraekeveltView Answer on Stackoverflow