These answers made me really close, although they did have some flaws:
The first time I took ZS advice and made it a category in NSManagedObject, it seemed a little cleaner to me.
2nd, My object graph contains a one relationship, so I started with a levous example, but note that the levous example does not clone an object in the case of a one relationship. This will crash (attempt to save the NSMO from one context in another context). I turned to this in the following example.
Thirdly, I provided a cache of already cloned objects, this prevents cloning of objects twice and therefore is duplicated in a new graph of objects, and also prevents loops.
Fourth, I added a blacklist (a list of Entity types for cloning). I did this in part to solve one of the shortcomings of my final decision, which I will discuss below.
NOTE. If you use what I understand as a best practice for CoreData, always providing backward relationships, then this will probably clone all objects that are related to the object you want to clone. If you use inversions and you have one root object that knows about all the other objects, then you are likely to clone all of this. My solution was to add a blacklist and pass to the Entity type, which, as I knew, was the parent of one of the objects that I wanted to clone. This seems 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
Derrick Sep 30 '11 at 16:35 2011-09-30 16:35
source share