Skip to content

Instantly share code, notes, and snippets.

@shto
Last active April 20, 2018 04:53
Show Gist options
  • Save shto/9552503 to your computer and use it in GitHub Desktop.
Save shto/9552503 to your computer and use it in GitHub Desktop.
NSManagedObject cloner files
#import <CoreData/CoreData.h>
@interface NSManagedObject (Cloner)
- (NSManagedObject *)clone;
- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)differentContext;
@end
#import "NSManagedObject+Cloner.h"
@implementation NSManagedObject (Cloner)
// modify this variable to go deeper into relationships
#define kMaxDepth 2
- (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)differentContext {
return [self cloneWithCopyCache:[NSMutableDictionary dictionary]
excludeEntities:@[]
currentDepth:0
inContext:differentContext];
}
- (NSManagedObject *)clone {
return [self cloneWithCopyCache:[NSMutableDictionary dictionary]
excludeEntities:@[]
currentDepth:0
inContext:self.managedObjectContext];
}
// Returns a deep-copy of this object
- (NSManagedObject *)cloneWithCopyCache:(NSMutableDictionary *)alreadyCopiedObjects
excludeEntities:(NSArray *)namesOfEntitiesToExclude
currentDepth:(NSInteger)depth
inContext:(NSManagedObjectContext *)moc
{
if ([namesOfEntitiesToExclude containsObject:self.entity.name]) {
// NSLog(@"< back");
return nil;
}
// NSLog(@"\t\t\t---In %@---", self.entity.name);
if (depth > kMaxDepth) {
return nil;
}
NSEntityDescription *entity = self.entity;
__block id selfCopy = nil;
/**
Return the object if it is already in the cache; if we don't return it at this point, we will go into a cycle
*/
NSManagedObject *cloned = [alreadyCopiedObjects objectForKey:self.objectID];
if (cloned != nil) {
return cloned;
} else {
selfCopy = [[[self class] alloc] initWithEntity:entity insertIntoManagedObjectContext:moc];
[alreadyCopiedObjects setObject:selfCopy forKey:self.objectID];
}
// attributes
[self.entity.attributesByName.allKeys enumerateObjectsUsingBlock:
^(NSString *attrKey, NSUInteger idx, BOOL *stop)
{
id valueForKey = [[self valueForKey:attrKey] copy];
[selfCopy setValue:valueForKey forKey:attrKey];
}];
// relationships
[self.entity.relationshipsByName.allKeys enumerateObjectsUsingBlock:
^(NSString *relationshipName, NSUInteger idx, BOOL *stop)
{
NSRelationshipDescription *rel = [self.entity.relationshipsByName
objectForKey:relationshipName];
if ([rel isToMany]) {
NSInteger nextDepth = depth + 1;
// NSLog(@"1-*:\t\t%@\t\t|\tdepth:%ld", rel.name, (long)_depth);
// either ordered or unordered set
id allObjectsForToManyKey = [self valueForKey:relationshipName];
id copyOfAllObjectsForToManyKey = nil;
if ([allObjectsForToManyKey isKindOfClass:[NSSet class]]) {
copyOfAllObjectsForToManyKey = [[NSMutableSet alloc] init];
} else if ([allObjectsForToManyKey isKindOfClass:[NSOrderedSet class]]) {
copyOfAllObjectsForToManyKey = [[NSMutableOrderedSet alloc] init];
}
// one to many relationship - go through each object within
for (NSManagedObject *objectInSet in allObjectsForToManyKey) {
NSManagedObject *objectInSetCopy =
[objectInSet cloneWithCopyCache:alreadyCopiedObjects
excludeEntities:namesOfEntitiesToExclude
currentDepth:nextDepth
inContext:moc];
// objectInSetCopy could be nil if we've reached maximum depth
if (objectInSetCopy &&
[copyOfAllObjectsForToManyKey
respondsToSelector:@selector(addObject:)])
{
[copyOfAllObjectsForToManyKey performSelector:@selector(addObject:)
withObject:objectInSetCopy];
}
}
[selfCopy setValue:copyOfAllObjectsForToManyKey forKey:relationshipName];
} else {
NSInteger nextDepth = depth + 1;
// NSLog(@"1-1:\t\t%@\t\t|\tdepth: %ld", rel.name, (long)_depth);
NSManagedObject *objectForRelationship = [self valueForKey:relationshipName];
NSManagedObject *copyOfObjectForRelationship =
[objectForRelationship cloneWithCopyCache:alreadyCopiedObjects
excludeEntities:namesOfEntitiesToExclude
currentDepth:nextDepth
inContext:moc];
[selfCopy setValue:copyOfObjectForRelationship forKey:relationshipName];
}
}];
return selfCopy;
}
@end
@HighKo
Copy link

HighKo commented Mar 25, 2014

Hi,

using automatic tests I found one error.
In line 41 you create a copy before checking if a copy already exists within the cache.

In case it already exists the second copy lives within the context with nil attributes.
It can be fixed by creating the copy after line 51.

@shto
Copy link
Author

shto commented Mar 31, 2014

@HighKo - Excellent find, thank you very much! I've updated the code.

@HighKo
Copy link

HighKo commented Jan 15, 2015

Using setValue instead of setPrimitiveValue might trigger side effects of overwritten setters. Likewise for getters.

@redfearnk
Copy link

The opposite can also be true. You might have unintended consequences from calling setPrimitiveValue instead of setValue. It just depends on what you want.

@Chavenay
Copy link

I think it is not necessary using copy for attributes

[self.entity.attributesByName.allKeys enumerateObjectsUsingBlock:
^(NSString *attrKey, NSUInteger idx, BOOL *stop)
{
    [selfCopy setValue:[self valueForKey:attrKey] forKey:attrKey];
}];

@murad1981
Copy link

nice category .. is there any effort for a swift equivalent ?

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