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

This comment has been minimized.

Show comment Hide comment
@jdklub

jdklub Apr 7, 2010

This was incredibly helpful. Thanks so much for sharing.

jdklub commented Apr 7, 2010

This was incredibly helpful. Thanks so much for sharing.

@dpilone

This comment has been minimized.

Show comment Hide comment
@dpilone

dpilone Jul 13, 2010

Unless I'm mistaken, line 29 should be:

[copiedObject release];

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

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

This comment has been minimized.

Show comment Hide comment
@robrix

robrix Jul 14, 2010

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

Owner

robrix commented Jul 14, 2010

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

@djones

This comment has been minimized.

Show comment Hide comment
@djones

djones Sep 22, 2010

really useful! thanks

djones commented Sep 22, 2010

really useful! thanks

@robrix

This comment has been minimized.

Show comment Hide comment
@robrix

robrix Sep 22, 2010

Glad I could help :)

Owner

robrix commented Sep 22, 2010

Glad I could help :)

@asymptotik

This comment has been minimized.

Show comment Hide comment
@asymptotik

asymptotik Feb 28, 2012

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.

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

This comment has been minimized.

Show comment Hide comment
@robrix

robrix Feb 28, 2012

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

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

This comment has been minimized.

Show comment Hide comment
@shmidt

shmidt Dec 13, 2013

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

shmidt commented Dec 13, 2013

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

@VincentSit

This comment has been minimized.

Show comment Hide comment
@VincentSit

VincentSit Jan 3, 2014

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

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

@cmaldonados

This comment has been minimized.

Show comment Hide comment
@cmaldonados

cmaldonados Jan 16, 2014

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.

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

This comment has been minimized.

Show comment Hide comment
@anthonyHall650

anthonyHall650 Mar 29, 2015

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

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

@Sajjon

This comment has been minimized.

Show comment Hide comment
@Sajjon

Sajjon Sep 13, 2016

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

Sajjon commented Sep 13, 2016

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

@gerasumit

This comment has been minimized.

Show comment Hide comment
@gerasumit

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

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