Last active
May 19, 2017 18:46
-
-
Save jpmhouston/7958fceae9216f69178d4719a3492577 to your computer and use it in GitHub Desktop.
NSManagedObject+JPHClone
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NSManagedObject+Clone.h | |
// TResActivityLog | |
// | |
// Created by Pierre Houston on 2016-05-30. | |
// Copyright © 2016 Resilience software. All rights reserved. | |
// | |
// When no context provided, uses [NSManagedObjectContext MR_defaultContext]. | |
// Note, I was going to add unique prefix "jph_" to these category method names, | |
// but currently I've left those out. | |
// | |
#import <CoreData/CoreData.h> | |
NS_ASSUME_NONNULL_BEGIN | |
@interface NSManagedObject (JPHCloneWithMagicalRecord) | |
- (instancetype)shallowClone; | |
- (instancetype)shallowCloneInContext:(NSManagedObjectContext *)context; | |
- (instancetype)deepClone; | |
- (instancetype)deepCloneInContext:(NSManagedObjectContext *)context; | |
- (instancetype)deepCloneExceptForRelationshipKeyPaths:(NSArray *)omitKeyPaths; | |
- (instancetype)deepCloneExceptForRelationshipKeyPaths:(NSArray *)omitKeyPaths inContext:(NSManagedObjectContext *)context; | |
@end | |
NS_ASSUME_NONNULL_END |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NSManagedObject+Clone.m | |
// TResActivityLog | |
// | |
// Created by Pierre Houston on 2016-05-30. | |
// Copyright © 2016 Resilience software. All rights reserved. | |
// | |
// Inspired by http://stackoverflow.com/a/2936660 & http://pastebin.com/efkji4sy | |
// | |
#import "NSManagedObject+Clone.h" | |
#import <MagicalRecord/MagicalRecord.h> | |
NS_ASSUME_NONNULL_BEGIN | |
@implementation NSManagedObject (CloneWithMagicalRecord) | |
- (instancetype)shallowClone | |
{ | |
return [self cloneDeep:NO exceptForRelationshipKeyPaths:@[] omittingInverseKeyName:nil inContext:[NSManagedObjectContext MR_defaultContext]]; | |
} | |
- (instancetype)deepClone | |
{ | |
return [self cloneDeep:YES exceptForRelationshipKeyPaths:@[] omittingInverseKeyName:nil inContext:[NSManagedObjectContext MR_defaultContext]]; | |
} | |
- (instancetype)deepCloneExceptForRelationshipKeyPaths:(NSArray *)omitKeyPaths | |
{ | |
return [self cloneDeep:YES exceptForRelationshipKeyPaths:omitKeyPaths omittingInverseKeyName:nil inContext:[NSManagedObjectContext MR_defaultContext]]; | |
} | |
- (instancetype)shallowCloneInContext:(NSManagedObjectContext *)context | |
{ | |
return [self cloneDeep:NO exceptForRelationshipKeyPaths:@[] omittingInverseKeyName:nil inContext:context]; | |
} | |
- (instancetype)deepCloneInContext:(NSManagedObjectContext *)context | |
{ | |
return [self cloneDeep:YES exceptForRelationshipKeyPaths:@[] omittingInverseKeyName:nil inContext:context]; | |
} | |
- (instancetype)deepCloneExceptForRelationshipKeyPaths:(NSArray *)omitKeyPaths inContext:(NSManagedObjectContext *)context | |
{ | |
return [self cloneDeep:YES exceptForRelationshipKeyPaths:omitKeyPaths omittingInverseKeyName:nil inContext:context]; | |
} | |
- (instancetype)cloneDeep:(BOOL)deep exceptForRelationshipKeyPaths:(NSArray *)omitKeyPaths omittingInverseKeyName:(nullable NSString *)inverseKeyName inContext:(NSManagedObjectContext *)context | |
{ | |
// create new object in data store | |
NSManagedObject *cloned = [[self class] MR_createEntityInContext:context]; | |
if (cloned == nil) { | |
return nil; | |
} | |
NSEntityDescription *entityDescription = [[self class] MR_entityDescriptionInContext:context]; | |
// loop through all attributes and assign then to the clone | |
NSDictionary *attributes = [entityDescription attributesByName]; | |
for (NSString *attr in attributes) { | |
[cloned setValue:[self valueForKey:attr] forKey:attr]; | |
} | |
// loop through all relationships, and clone them | |
NSDictionary *relationships = [entityDescription relationshipsByName]; | |
for (NSString *keyName in relationships.allKeys) { | |
// don't create inverse relationship | |
if (inverseKeyName != nil && [keyName isEqualToString:inverseKeyName]) { | |
continue; | |
} | |
NSRelationshipDescription *rel = relationships[keyName]; | |
BOOL clone = deep; | |
// parse `omitKeyPaths` removing all except if prefixed by `keyName` - include those with `keyName` stripped | |
// and if `omitKeyPaths` includes `keyName` exactly then don't clone this relationship | |
NSMutableArray *subKeyPaths = nil; | |
if (omitKeyPaths != nil) { | |
subKeyPaths = [NSMutableArray array]; | |
for (NSString *keyPath in omitKeyPaths) { | |
NSArray *keyPathComponents = [keyPath componentsSeparatedByString:@"."]; | |
if ([keyPathComponents.firstObject isEqualToString:keyName]) { | |
if (keyPathComponents.count == 1) { | |
clone = NO; | |
} else { | |
NSArray *remainingKeyPathComponents = [keyPathComponents subarrayWithRange:NSMakeRange(1, keyPathComponents.count - 1)]; | |
[subKeyPaths addObject:[remainingKeyPathComponents componentsJoinedByString:@"."]]; | |
} | |
} | |
} | |
} | |
// also omits the inverse relationship back to this object so as not to recursively clone self | |
// get a set of all objects in the relationship | |
if (rel.toMany && rel.ordered) { | |
NSMutableOrderedSet *sourceSet = [self mutableOrderedSetValueForKey:keyName]; | |
NSMutableOrderedSet *clonedSet = [cloned mutableOrderedSetValueForKey:keyName]; | |
if (sourceSet == nil || clonedSet == nil) { | |
NSLog(@"while cloning %@ %p, unable to access both source & destination relationship orderedsets, src %p dst %p", self.class, self, sourceSet, clonedSet); | |
} | |
else if (clone) { | |
for (NSManagedObject *relatedObject in [sourceSet reverseObjectEnumerator]) { // iterate in reverse! because found that when added in the correct order, they end up backwards! | |
// clone it, and add clone to set | |
NSManagedObject *clonedRelatedObject = [relatedObject cloneDeep:YES exceptForRelationshipKeyPaths:subKeyPaths omittingInverseKeyName:rel.inverseRelationship.name inContext:context]; | |
if (clonedRelatedObject == nil) { | |
NSLog(@"while cloning %@ %p, failed to clone relationship object %@ %@. NB. partial failure handling could be improved.", self.class, self, relatedObject.class, keyName); | |
} else { | |
[clonedSet addObject:clonedRelatedObject]; | |
} | |
} | |
} | |
else if (!rel.inverseRelationship || rel.inverseRelationship.toMany) { // if not cloning and reverse relationship is to-one, can't duplicate since that would voilate the to-one-ness | |
for (NSManagedObject *relatedObject in [sourceSet reverseObjectEnumerator]) { // iterate in reverse, see above | |
[clonedSet addObject:relatedObject]; | |
} | |
} | |
} | |
else if (rel.toMany) { | |
NSMutableSet *sourceSet = [self mutableSetValueForKey:keyName]; | |
NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName]; | |
if (sourceSet == nil || clonedSet == nil) { | |
NSLog(@"while cloning %@ %p, unable to access both source & destination relationship orderedsets, src %p dst %p", self.class, self, sourceSet, clonedSet); | |
} | |
else if (clone) { | |
for (NSManagedObject *relatedObject in sourceSet) { | |
// clone it, and add clone to set | |
NSManagedObject *clonedRelatedObject = [relatedObject cloneDeep:YES exceptForRelationshipKeyPaths:subKeyPaths omittingInverseKeyName:rel.inverseRelationship.name inContext:context]; | |
if (clonedRelatedObject == nil) { | |
NSLog(@"while cloning %@ %p, failed to clone relationship object %@ %@. NB. partial failure handling could be improved.", self.class, self, relatedObject.class, keyName); | |
} else { | |
[clonedSet addObject:clonedRelatedObject]; | |
} | |
} | |
} | |
else if (!rel.inverseRelationship || rel.inverseRelationship.toMany) { // if not cloning and reverse relationship is to-one, can't duplicate since that would voilate the to-one-ness | |
[clonedSet unionSet:sourceSet]; | |
} | |
} | |
else { | |
NSManagedObject *sourceObject = (NSManagedObject *)[self valueForKey:keyName]; | |
if (sourceObject == nil) { | |
NSLog(@"while cloning %@ %p, unable to access source relationship object, %p", self.class, self, sourceObject); | |
} | |
else if (clone) { | |
NSManagedObject *clonedObject = [sourceObject cloneDeep:YES exceptForRelationshipKeyPaths:subKeyPaths omittingInverseKeyName:rel.inverseRelationship.name inContext:context]; | |
if (clonedObject == nil) { | |
NSLog(@"while cloning %@ %p, failed to clone relationship object %@ %@. NB. partial failure handling could be improved.", self.class, self, sourceObject.class, keyName); | |
} else { | |
[cloned setValue:clonedObject forKey:keyName]; | |
} | |
} | |
else if (!rel.inverseRelationship || rel.inverseRelationship.toMany) { // if not cloning and reverse relationship is to-one, can't duplicate since that would voilate the to-one-ness | |
[cloned setValue:sourceObject forKey:keyName]; | |
} | |
} | |
} | |
return cloned; | |
} | |
@end | |
NS_ASSUME_NONNULL_END |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
#import "NSManagedObject+Clone.h"
#import "NSManagedObject+JPHClone.h"