Created
October 29, 2011 10:28
-
-
Save robertjpayne/1324319 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 |
Hey, I had the same problem, I solved it changing the line which uses attributeKeys by the following and it works fine:
NSMutableArray* keys = [NSMutableArray array];
for (NSString* key in entityDescription.attributesByName.keyEnumerator) {
[keys addObject:key];
}
[propertyList setObject:[pObject dictionaryWithValuesForKeys:keys] forKey:@"attributes"];
forked and included dprados' change
Is there any reason not to use entityDescription.attributesByName.allKeys
to save having to do a loop ourself?
@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
Is there a version of this that works with iOS 7.X? It's not compiling. Stating that "Property 'attributeKeys' not found on object of type 'NSEntityDescription'