blob: 391eb8294462cea99dba85b2a86317beceb2a6fd [file] [log] [blame]
//
// 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