-
-
Save danieleangeli/2659057 to your computer and use it in GitHub Desktop.
NSManagedObject Archiving and Unarchiving
This file contains 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
#import <Foundation/Foundation.h> | |
@interface NSManagedObjectArchiver : NSObject | |
/* | |
* Takes a NSManagedObject and converts it to a NSData archive - it traverses all relationships ( including circular ) and archives it | |
*/ | |
+ (NSData *)archivedDataWithRootObject:(NSManagedObject *)pObject; | |
@end | |
@interface NSManagedObjectUnarchiver : NSObject | |
/* | |
* Takes a NSData object from NSManagedObjectArchiver and re-creates the NSManagedObject | |
* @param NSManagedObjectContext *context - required - provided context is used to obtain NSEntityDescriptions from. The entityName and entityVersionHash is checked to restore the correct entity. | |
* @param BOOL insert - whether or not objects should be inserted into the provided context when created, if choose not to insert them you would need to insert them manually ( including relationships ) before they can be saved | |
*/ | |
+ (NSManagedObject *)unarchiveObjectWithData:(NSData *)pData context:(NSManagedObjectContext *)pContext insert:(BOOL)pInsert; | |
@end | |
@interface NSManagedObject (NSManagedObjectCopying) | |
/* | |
* Makes a copy of an NSManagedObject - this is a shallow copy and does not traverse relationships | |
* @param NSManagedObjectContext *context - required - provided context is used to obtain NSEntityDescriptions from. | |
* @param BOOL insert - whether or not objects should be inserted into the provided context when created, if choose not to insert them you would need to insert them manually ( including relationships ) before they can be saved | |
*/ | |
- (id)copyUsingContext:(NSManagedObjectContext *)pContext insert:(BOOL)pInsert; | |
/* | |
* Makes a copy of an NSManagedObject - this is a deep copy and does traverse relationships ( including circular ) | |
* @param NSManagedObjectContext *context - required - provided context is used to obtain NSEntityDescriptions from. | |
* @param BOOL insert - whether or not objects should be inserted into the provided context when created, if choose not to insert them you would need to insert them manually ( including relationships ) before they can be saved | |
*/ | |
- (id)copyIncludingRelationshipsUsingContext:(NSManagedObjectContext *)pContext insert:(BOOL)pInsert; | |
@end |
This file contains 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
#import "NSManagedObjectArchiving.h" | |
@interface NSManagedObjectArchiver() | |
@property (nonatomic, strong) NSMutableDictionary *objects; | |
@property (nonatomic, strong) NSString *rootObjectIdentifier; | |
- (id)identifierForObject:(NSManagedObject *)pObject; | |
- (id)propertyListForObject:(NSManagedObject *)pObject; | |
- (id)propertyListForRelationshipWithName:(NSString *)pRelationshipName inObject:(NSManagedObject *)pObject; | |
@end | |
@implementation NSManagedObjectArchiver | |
#pragma mark - Properties | |
@synthesize objects; | |
@synthesize rootObjectIdentifier; | |
#pragma mark - Initialization | |
+ (NSData *)archivedDataWithRootObject:(NSManagedObject *)pRootObject { | |
NSManagedObjectArchiver *archiver = [[self alloc] init]; | |
archiver.rootObjectIdentifier = [archiver identifierForObject:pRootObject]; | |
[archiver propertyListForObject:pRootObject]; | |
return [NSKeyedArchiver archivedDataWithRootObject:[NSDictionary dictionaryWithObjectsAndKeys: | |
archiver.objects, @"objects", | |
archiver.rootObjectIdentifier, @"rootObjectIdentifier", | |
nil]]; | |
} | |
- (id)init { | |
if((self = [super init])) { | |
objects = [NSMutableDictionary dictionary]; | |
} | |
return self; | |
} | |
#pragma mark - Actions | |
- (id)identifierForObject:(NSManagedObject *)pObject { | |
if(pObject == nil) { | |
return [NSNull null]; | |
} | |
return pObject.objectID.URIRepresentation.absoluteString; | |
} | |
- (id)propertyListForObject:(NSManagedObject *)pObject { | |
if(pObject == nil) { | |
return [NSNull null]; | |
} | |
NSString *identifier = [self identifierForObject:pObject]; | |
id existingPropertyList = [self.objects objectForKey:identifier]; | |
if(existingPropertyList != nil) { | |
return existingPropertyList; | |
} | |
NSEntityDescription *entityDescription = pObject.entity; | |
NSMutableDictionary *propertyList = [NSMutableDictionary dictionary]; | |
[propertyList setObject:entityDescription.name forKey:@"entityName"]; | |
[propertyList setObject:entityDescription.versionHash forKey:@"entityVersionHash"]; | |
[propertyList setObject:identifier forKey:@"identifier"]; | |
[propertyList setObject:[pObject dictionaryWithValuesForKeys:entityDescription.attributeKeys] forKey:@"attributes"]; | |
[self.objects setObject:propertyList forKey:identifier]; | |
NSMutableDictionary *propertyListRelationships = [NSMutableDictionary dictionary]; | |
[propertyList setObject:propertyListRelationships forKey:@"relationships"]; | |
for(NSString *relationshipName in entityDescription.relationshipsByName) { | |
[propertyListRelationships setObject:[self propertyListForRelationshipWithName:relationshipName inObject:pObject] | |
forKey:relationshipName]; | |
} | |
return propertyList; | |
} | |
- (id)propertyListForRelationshipWithName:(NSString *)pRelationshipName inObject:(NSManagedObject *)pObject { | |
NSRelationshipDescription *relationshipDescription = [pObject.entity.relationshipsByName objectForKey:pRelationshipName]; | |
if(relationshipDescription.isToMany) { | |
id src = [pObject valueForKey:pRelationshipName]; | |
id dst = nil; | |
if(relationshipDescription.isOrdered) { | |
dst = [NSMutableOrderedSet orderedSet]; | |
} else { | |
dst = [NSMutableSet set]; | |
} | |
for(NSManagedObject *object in src) { | |
NSString *identifier = [self identifierForObject:object]; | |
[self propertyListForObject:object]; | |
[dst addObject:identifier]; | |
} | |
return ([dst count] > 0) ? dst : [NSNull null]; | |
} else { | |
NSString *identifier = [self identifierForObject:[pObject valueForKey:pRelationshipName]]; | |
[self propertyListForObject:[pObject valueForKey:pRelationshipName]]; | |
return identifier; | |
} | |
} | |
@end | |
@interface NSManagedObjectUnarchiver() | |
@property (nonatomic, strong) NSManagedObjectContext *context; | |
@property (nonatomic, assign) BOOL insert; | |
@property (nonatomic, strong) NSMutableDictionary *entities; | |
@property (nonatomic, strong) NSDictionary *archivedObjects; | |
@property (nonatomic, strong) NSMutableDictionary *unarchivedObjects; | |
- (NSManagedObject *)objectForIdentifier:(NSString *)pIdentifier; | |
- (id)objectsForIdentifiers:(id)pIdentifiers; | |
@end | |
@implementation NSManagedObjectUnarchiver | |
#pragma mark - Properties | |
@synthesize context; | |
@synthesize insert; | |
@synthesize entities; | |
@synthesize archivedObjects; | |
@synthesize unarchivedObjects; | |
#pragma mark - Initialization | |
+ (NSManagedObject *)unarchiveObjectWithData:(NSData *)pData context:(NSManagedObjectContext *)pContext insert:(BOOL)pInsert { | |
NSManagedObjectUnarchiver *unarchiver = [[NSManagedObjectUnarchiver alloc] init]; | |
NSDictionary *dictionary = [NSKeyedUnarchiver unarchiveObjectWithData:pData]; | |
if(dictionary != nil && [dictionary isKindOfClass:[NSDictionary class]]) { | |
unarchiver.context = pContext; | |
unarchiver.insert = pInsert; | |
unarchiver.archivedObjects = [dictionary objectForKey:@"objects"]; | |
return [unarchiver objectForIdentifier:[dictionary objectForKey:@"rootObjectIdentifier"]]; | |
} | |
return nil; | |
} | |
- (id)init { | |
if((self = [super init])) { | |
unarchivedObjects = [NSMutableDictionary dictionary]; | |
} | |
return self; | |
} | |
#pragma mark - Actions | |
- (NSManagedObject *)objectForIdentifier:(NSString *)pIdentifier { | |
if(pIdentifier == nil || ![pIdentifier isKindOfClass:[NSString class]]) { | |
return nil; | |
} | |
NSManagedObject *unarchivedObject = [self.unarchivedObjects objectForKey:pIdentifier]; | |
if(unarchivedObject != nil) { | |
return unarchivedObject; | |
} | |
NSDictionary *archivedObject = [self.archivedObjects objectForKey:pIdentifier]; | |
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:[archivedObject objectForKey:@"entityName"] inManagedObjectContext:self.context]; | |
if(![entityDescription.versionHash isEqualToData:[archivedObject objectForKey:@"entityVersionHash"]]) { | |
[NSException raise:@"Invalid archived data" format:@"Mismatching version hashes, archived managed object's entity version hash differs from version hash in the provided context."]; | |
return nil; | |
} | |
Class entityClass = NSClassFromString([entityDescription managedObjectClassName]); | |
if(entityClass == nil) { | |
[NSException raise:@"Invalid archived data" format:@"Cannot find custom class with name %@ for managed object with entity name %@", entityDescription.managedObjectClassName, entityDescription.name]; | |
return nil; | |
} | |
NSManagedObject *object = [[entityClass alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:(self.insert) ? self.context : nil]; | |
[object setValuesForKeysWithDictionary:[archivedObject objectForKey:@"attributes"]]; | |
[self.unarchivedObjects setObject:object forKey:pIdentifier]; | |
NSDictionary *relationships = [archivedObject objectForKey:@"relationships"]; | |
for(NSString *relationshipName in relationships) { | |
id relationshipValue = [relationships objectForKey:relationshipName]; | |
if([relationshipValue isKindOfClass:[NSString class]]) { | |
relationshipValue = [self objectForIdentifier:relationshipValue]; | |
} else if([relationshipValue isKindOfClass:[NSOrderedSet class]] || | |
[relationshipValue isKindOfClass:[NSSet class]]) { | |
relationshipValue = [self objectsForIdentifiers:relationshipValue]; | |
} else if([relationshipValue isKindOfClass:[NSNull class]]) { | |
relationshipValue = nil; | |
} | |
[object setValue:relationshipValue forKey:relationshipName]; | |
} | |
return object; | |
} | |
- (id)objectsForIdentifiers:(id)pIdentifiers { | |
if(pIdentifiers == nil) { | |
return nil; | |
} | |
id collection = nil; | |
if([pIdentifiers isKindOfClass:[NSOrderedSet class]]) { | |
collection = [NSMutableOrderedSet orderedSetWithCapacity:[pIdentifiers count]]; | |
} else if([pIdentifiers isKindOfClass:[NSSet class]]) { | |
collection = [NSMutableSet setWithCapacity:[pIdentifiers count]]; | |
} else { | |
return nil; | |
} | |
for(id identifier in pIdentifiers) { | |
NSManagedObject *object = [self objectForIdentifier:identifier]; | |
if(object != nil) { | |
[collection addObject:object]; | |
} | |
} | |
return ([collection count] > 0) ? collection : nil; | |
} | |
@end | |
@implementation NSManagedObject (NSManagedObjectCopying) | |
- (id)copyUsingContext:(NSManagedObjectContext *)pContext insert:(BOOL)pInsert { | |
NSEntityDescription *entity = [NSEntityDescription entityForName:self.entity.name inManagedObjectContext:pContext]; | |
Class entityClass = NSClassFromString(entity.managedObjectClassName); | |
NSManagedObject *copy = [[entityClass alloc] initWithEntity:entity insertIntoManagedObjectContext:((pInsert) ? pContext : nil)]; | |
[copy setValuesForKeysWithDictionary:[self dictionaryWithValuesForKeys:self.entity.attributeKeys]]; | |
return copy; | |
} | |
- (id)copyIncludingRelationshipsUsingContext:(NSManagedObjectContext *)pContext insert:(BOOL)pInsert { | |
NSData *d = [NSManagedObjectArchiver archivedDataWithRootObject:self]; | |
return [NSManagedObjectUnarchiver unarchiveObjectWithData:d context:pContext insert:pInsert]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment