Create a gist now

Instantly share code, notes, and snippets.

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
jdklub commented Apr 7, 2010

This was incredibly helpful. Thanks so much for sharing.

@dpilone
dpilone commented Jul 13, 2010

Unless I'm mistaken, line 29 should be:

[copiedObject release];

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

@robrix
Owner
robrix commented Jul 14, 2010

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

@djones
djones commented Sep 22, 2010

really useful! thanks

@robrix
Owner
robrix commented Sep 22, 2010

Glad I could help :)

@asymptotik

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.

@robrix
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
shmidt commented Dec 13, 2013

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

@VincentSit

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

@cmaldonados

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.

@anthonyHall650

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

@Sajjon
Sajjon commented Sep 13, 2016

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

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