| // |
| // BKController.m |
| // Broker |
| // |
| // Created by Andrew Smith on 1/8/14. |
| // Copyright (c) 2014 Andrew B. Smith. All rights reserved. |
| // |
| |
| #import "BKController.h" |
| |
| #import "BKEntityMap.h" |
| #import "BKJSONController.h" |
| #import "BKEntityDescription.h" |
| #import "BKAttributeDescription.h" |
| #import "BrokerLog.h" |
| |
| static NSString * const BROKER_INTERNAL_QUEUE = @"com.broker.queue"; |
| |
| @interface BKController () |
| |
| @property (nonatomic, strong, readwrite) BKEntityMap *entityMap; |
| |
| /** |
| Internal serial queue for core data processing off the main thread. |
| */ |
| @property (nonatomic, strong) NSOperationQueue *queue; |
| |
| @end |
| |
| @implementation BKController |
| |
| + (instancetype)controller |
| { |
| BKController *controller = [self new]; |
| |
| // |
| // Entity Map |
| // |
| BKEntityMap *entityMap = [BKEntityMap entityMap]; |
| controller.entityMap = entityMap; |
| |
| // |
| // Serial queue |
| // |
| NSOperationQueue *queue = [NSOperationQueue new]; |
| queue.maxConcurrentOperationCount = 1; |
| controller.queue = queue; |
| |
| return controller; |
| } |
| |
| #pragma mark BKJSONController |
| |
| - (void)processJSONObject:(NSDictionary *)json |
| asEntityNamed:(NSString *)entityName |
| inContext:(NSManagedObjectContext *)context |
| completionBlock:(void (^)())completionBlock |
| { |
| NSOperation *operation = [self operationForContext:context |
| JSON:json |
| withJSONBlock:^(id jsonCopy, |
| NSManagedObjectContext *backgroundContext, |
| BKJSONController *jsonController) { |
| [jsonController processJSONObject:jsonCopy |
| asEntityNamed:entityName]; |
| }]; |
| |
| // Finish |
| operation.completionBlock = completionBlock; |
| |
| // Queue it up |
| [self.queue addOperation:operation]; |
| } |
| |
| - (void)processJSONObject:(NSDictionary *)json |
| onObject:(NSManagedObject *)object |
| completionBlock:(void (^)())completionBlock |
| { |
| NSManagedObjectID *objectId = object.objectID; |
| |
| NSOperation *operation = [self operationForContext:object.managedObjectContext |
| JSON:json |
| withJSONBlock:^(id jsonCopy, |
| NSManagedObjectContext *backgroundContext, |
| BKJSONController *jsonController) { |
| // |
| // Use a newly fetched object from the background context so we don't modify |
| // the passed in object, which could come from god knows where. |
| // |
| NSManagedObject *backgroundObject = [backgroundContext objectWithID:objectId]; |
| |
| [jsonController processJSONObject:jsonCopy |
| onObject:backgroundObject]; |
| }]; |
| |
| // Finish |
| operation.completionBlock = completionBlock; |
| |
| // Queue it up |
| [self.queue addOperation:operation]; |
| |
| } |
| |
| - (void)processJSON:(id)json |
| forRelationship:(NSString *)relationshipName |
| onObject:(NSManagedObject *)object |
| completionBlock:(void (^)())completionBlock |
| { |
| NSManagedObjectID *objectId = object.objectID; |
| |
| // Operation |
| NSOperation *operation = [self operationForContext:object.managedObjectContext |
| JSON:json |
| withJSONBlock:^(id jsonCopy, |
| NSManagedObjectContext *backgroundContext, |
| BKJSONController *jsonController) { |
| // |
| // Use a newly fetched object from the background context so we don't modify |
| // the passed in object, which could come from god knows where. |
| // |
| NSManagedObject *backgroundObject = [backgroundContext objectWithID:objectId]; |
| |
| [jsonController processJSON:jsonCopy |
| forRelationship:relationshipName |
| onObject:backgroundObject]; |
| }]; |
| |
| // Finish |
| operation.completionBlock = completionBlock; |
| |
| // Queue it up |
| [self.queue addOperation:operation]; |
| } |
| |
| - (void)processJSONCollection:(NSArray *)json |
| asEntitiesNamed:(NSString *)entityName |
| inContext:(NSManagedObjectContext *)context |
| completionBlock:(void (^)())completionBlock |
| { |
| // Operation |
| NSOperation *operation = [self operationForContext:context |
| JSON:json |
| withJSONBlock:^(id jsonCopy, |
| NSManagedObjectContext *backgroundContext, |
| BKJSONController *jsonController) { |
| [jsonController processJSONCollection:jsonCopy |
| asEntitiesNamed:entityName]; |
| }]; |
| |
| // Finish |
| operation.completionBlock = completionBlock; |
| |
| // Queue it up |
| [self.queue addOperation:operation]; |
| } |
| |
| - (void)processJSON:(id)json |
| workBlock:(void (^)(id jsonCopy, NSManagedObjectContext *childContext, BKJSONController *jsonController))workBlock |
| inContext:(NSManagedObjectContext *)context |
| completionBlock:(void (^)())completionBlock |
| { |
| // Operation |
| NSOperation *operation = [self operationForContext:context |
| JSON:json |
| withJSONBlock:workBlock]; |
| |
| // Finish |
| operation.completionBlock = completionBlock; |
| |
| // Queue it up |
| [self.queue addOperation:operation]; |
| } |
| |
| #pragma mark - |
| |
| - (NSOperation *)operationForContext:(NSManagedObjectContext *)context |
| JSON:(id)json |
| withJSONBlock:(void (^)(id jsonCopy, |
| NSManagedObjectContext *childContext, |
| BKJSONController *jsonController))block |
| { |
| __weak typeof(self) weakSelf = self; |
| NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ |
| // Background context |
| NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; |
| backgroundContext.parentContext = context; |
| |
| // Memory optimization |
| backgroundContext.undoManager = nil; |
| |
| // Grab a new JSON controller |
| BKJSONController *jsonController = [BKJSONController JSONControllerWithContext:backgroundContext |
| entityMap:weakSelf.entityMap]; |
| |
| // Copy JSON to make sure it doesn't get mutated underneath us |
| id jsonCopy = [json copy]; |
| |
| // Queue up a blocking call to make sure we play super nice with Apples Core Data concurrency |
| [backgroundContext performBlockAndWait:^{ |
| /** |
| |
| Here is why an autorelease pool is necessary, per apple docs. |
| |
| https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW18 |
| |
| "If your block creates more than a few Objective-C objects, you might want to enclose parts |
| of your block’s code in an @autorelease block to handle the memory management for those |
| objects. Although GCD dispatch queues have their own autorelease pools, they make no |
| guarantees as to when those pools are drained. If your application is memory constrained, |
| creating your own autorelease pool allows you to free up the memory for autoreleased |
| objects at more regular intervals." |
| |
| */ |
| @autoreleasepool { |
| // Do the actual work |
| if (block) block(jsonCopy, backgroundContext, jsonController); |
| // Save background context. Does not automatically save parent context. |
| NSError *error; |
| [backgroundContext save:&error]; |
| if (error) { |
| BrokerLog(@"%@", error.localizedDescription); |
| } |
| } |
| }]; |
| }]; |
| return operation; |
| } |
| |
| @end |