Skip to content

Instantly share code, notes, and snippets.

@robertjpayne
Created October 29, 2011 10:28
Show Gist options
  • Save robertjpayne/1324319 to your computer and use it in GitHub Desktop.
Save robertjpayne/1324319 to your computer and use it in GitHub Desktop.
NSManagedObject Archiving and Unarchiving
#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
#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
@samskiter
Copy link

forked and included dprados' change

@samskiter
Copy link

Is there any reason not to use entityDescription.attributesByName.allKeys to save having to do a loop ourself?

@mittsh
Copy link

mittsh commented Feb 21, 2015

@robertjpayne I created a repository from this Gist a while ago: https://github.com/luckymarmot/NSManagedObjectArchiving and now just updated it to make it available through Cocoapods. I mentioned your name and put it under MIT License, hope you're fine with that. Let me know if you need some changes, or if you prefer that I transfer ownership of the repo to you, or ..anything... Cheers! -Micha

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