Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Copy and deep copy for NSManagedObject graphs.
/*
This makes some assumptions about your code, for example that you’ve got ownership rules set up properly. If a relationship to another entity uses the cascade deletion rule, it’s considered to be owned by the receiver, and thus will be copied by -deepCopyWithZone:.
*/
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface NSManagedObject (RXCopying) <NSCopying>
-(void)setRelationshipsToObjectsByIDs:(id)objects;
-(id)deepCopyWithZone:(NSZone *)zone;
-(NSDictionary *)ownedIDs;
@end
@implementation NSManagedObject (RXCopying)
// copying
-(id)deepCopyWithZone:(NSZone *)zone {
NSMutableDictionary *ownedIDs = [[self ownedIDs] mutableCopy];
NSManagedObjectContext *context = [self managedObjectContext];
id copied = [self copyWithZone: zone]; // -copyWithZone: copies the attributes for us
for(NSManagedObjectID *key in [ownedIDs allKeys]) { // deep copy relationships
id copiedObject = [[context objectWithID: key] copyWithZone: zone];
[ownedIDs setObject: copiedObject forKey: key];
[copiedObject release];
}
[copied setRelationshipsToObjectsByIDs: ownedIDs];
for(NSManagedObjectID *key in [ownedIDs allKeys]) {
[[ownedIDs objectForKey: key] setRelationshipsToObjectsByIDs: ownedIDs];
}
return copied;
}
-(id)copyWithZone:(NSZone *)zone { // shallow copy
NSManagedObjectContext *context = [self managedObjectContext];
id copied = [[[self class] allocWithZone: zone] initWithEntity: [self entity] insertIntoManagedObjectContext: context];
for(NSString *key in [[[self entity] attributesByName] allKeys]) {
[copied setValue: [self valueForKey: key] forKey: key];
}
for(NSString *key in [[[self entity] relationshipsByName] allKeys]) {
[copied setValue: [self valueForKey: key] forKey: key];
}
return copied;
}
-(void)setRelationshipsToObjectsByIDs:(id)objects {
id newReference = nil;
NSDictionary *relationships = [[self entity] relationshipsByName];
for(NSString *key in [relationships allKeys]) {
if([[relationships objectForKey: key] isToMany]) {
id references = [NSMutableSet set];
for(id reference in [self valueForKey: key]) {
if(newReference = [objects objectForKey: [reference objectID]]) {
[references addObject: newReference];
} else {
[references addObject: reference];
}
}
[self setValue: references forKey: key];
} else {
if(newReference = [objects objectForKey: [[self valueForKey: key] objectID]]) {
[self setValue: newReference forKey: key];
}
}
}
}
-(NSDictionary *)ownedIDs {
NSDictionary *relationships = [[self entity] relationshipsByName];
NSMutableDictionary *ownedIDs = [NSMutableDictionary dictionary];
for(NSString *key in [relationships allKeys]) {
id relationship = [relationships objectForKey: key];
if([relationship deleteRule] == NSCascadeDeleteRule) { // ownership
if([relationship isToMany]) {
for(id child in [self valueForKey: key]) {
[ownedIDs setObject: child forKey: [child objectID]];
[ownedIDs addEntriesFromDictionary: [child ownedIDs]];
}
} else {
id reference = [self valueForKey: key];
[ownedIDs setObject: reference forKey: [reference objectID]];
}
}
}
return ownedIDs;
}
@end

jdklub commented Apr 7, 2010

This was incredibly helpful. Thanks so much for sharing.

dpilone commented Jul 13, 2010

Unless I'm mistaken, line 29 should be:

[copiedObject release];

Otherwise I believe you're leaking the copiedObject. -- Dan

Owner

robrix commented Jul 14, 2010

You’re exactly right, Dan. Thanks for the catch. Update: Fixed!

djones commented Sep 22, 2010

really useful! thanks

Owner

robrix commented Sep 22, 2010

Glad I could help :)

I know this is a few years old, but shouldn't line 32 be 'copied' rather than 'self'. Otherwise self is getting updated in the copy.

Owner

robrix commented Feb 28, 2012

Good catch! This is what you get when you don’t unit test, folks: bugs that live for years.

shmidt commented Dec 13, 2013

@robrix Thanks for sharing :)
How should I call it on instance? [managedObject copy];?

I think you should changed the = to == in line 60 and line 68.

Using this category makes relationships to vanish from the 'from' object:

This is how I make the call

newDeepCopiedMO = [oldMO deepCopyWithZone:NULL];

child relationships in oldMO are successfully deep-copied to newDeepCopiedMO but removed from oldMO. Furthermore, changing attributes in newDeepCopiedMO after the copy changes them on oldMO as if it were the same object.

yes this does remove relationships from the old managed object. has anyone figured out how to fix it?

Sajjon commented Sep 13, 2016

Hmm does this support the case where a "to-many"-relationship is ordered?

gerasumit commented Mar 14, 2017

I get a crash on following line 80:

[ownedIDs setObject: reference forKey: [reference objectID]];

with reason: '* setObjectForKey: key cannot be nil'**, probably because [reference objectID] is nil.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment