-
-
Save robertjpayne/1324319 to your computer and use it in GitHub Desktop.
| #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 |
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'
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
can this serialize a list of NSManagedObjects though? In particular an NSArray of nsmanagedobjects.