Skip to content

Instantly share code, notes, and snippets.

@indragiek
Last active December 10, 2015 08:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indragiek/4409438 to your computer and use it in GitHub Desktop.
Save indragiek/4409438 to your computer and use it in GitHub Desktop.
//
// FGOManagedObjectContextStack.h
//
// Created by Indragie Karunaratne on 2012-12-23.
// Copyright (c) 2012 Indragie Karunaratne. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface FGOManagedObjectContextStack : NSObject
@property (nonatomic, strong, readonly) NSManagedObjectContext *backgroundContext;
@property (nonatomic, strong, readonly) NSManagedObjectContext *mainQueueContext;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
- (id)initWithModelName:(NSString *)modelName;
- (void)saveChanges:(void(^)(NSError *error))handler;
- (void)fetchWithRequest:(NSFetchRequest *)request
completion:(void(^)(NSArray *results, NSError *error))handler;
- (void)createObjectOfEntityName:(NSString *)entityName
configure:(void(^)(id object))configure
result:(void(^)(id object, NSError *error))result;
- (void)fetchOrCreateObjectWithRequest:(NSFetchRequest *)request
configure:(void(^)(id object))configure
result:(void(^)(id object, NSError *error))result;
#pragma mark - Non-threadsafe methods
// Should only be called form inside -performBlock: of the background MOC
- (id)backgroundObjectWithID:(NSManagedObjectID *)objectID;
// Should only be called from the main thread
- (id)mainQueueObjectWithID:(NSManagedObjectID *)objectID;
@end
//
// FGOManagedObjectContextStack.m
//
// Created by Indragie Karunaratne on 2012-12-23.
// Copyright (c) 2012 Indragie Karunaratne. All rights reserved.
//
#import "FGOManagedObjectContextStack.h"
@implementation FGOManagedObjectContextStack
- (id)initWithModelName:(NSString *)modelName
{
if ((self = [super init])) {
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *appSupportURL = [[fm URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *dataURL = [appSupportURL URLByAppendingPathComponent:@"com.indragie.Flamingo"];
NSError *fileError = nil;
[fm createDirectoryAtURL:dataURL withIntermediateDirectories:YES attributes:nil error:&fileError];
if (fileError)
FGOGenericErrorLog(@"Error creating application data directory", fileError);
NSURL *storeURL = [dataURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.fgodata", modelName]];
NSError *coreDataError = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&coreDataError]) {
FGOGenericErrorLog(@"Error adding persistent store", coreDataError);
return nil;
}
_mainQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainQueueContext performBlockAndWait:^{
_mainQueueContext.persistentStoreCoordinator = _persistentStoreCoordinator;
_mainQueueContext.undoManager = nil;
}];
_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_backgroundContext performBlockAndWait:^{
_backgroundContext.parentContext = _mainQueueContext;
_backgroundContext.undoManager = nil;
}];
}
return self;
}
#pragma mark - Public Methods
- (void)saveChanges:(void(^)(NSError *error))handler
{
[self.backgroundContext performBlock:^{
__block NSError *error = nil;
[self.backgroundContext save:&error];
[self.mainQueueContext performBlock:^{
[self.mainQueueContext save:&error];
if (handler) handler(error);
}];
}];
}
- (void)fetchWithRequest:(NSFetchRequest *)request
completion:(void(^)(NSArray *results, NSError *error))handler
{
[request setResultType:NSManagedObjectIDResultType];
void (^executeHandler)(NSArray *, NSError *) = ^(NSArray *results, NSError *error){
dispatch_async(dispatch_get_main_queue(), ^{
if (handler) handler(results, error);
});
};
[self.backgroundContext performBlock:^{
NSError *error = nil;
NSArray *results = [self.backgroundContext executeFetchRequest:request error:&error];
if (error) executeHandler(nil, error);
else {
[self.backgroundContext reset];
[self.mainQueueContext performBlock:^{
NSArray *objects = [self _transformBackgroundObjectIDsToMainQueueObjects:results];
executeHandler(objects, nil);
}];
}
}];
}
- (void)createObjectOfEntityName:(NSString *)entityName
configure:(void(^)(id object))configure
result:(void(^)(id object, NSError *error))result
{
[self.backgroundContext performBlock:^{
id object = [NSEntityDescription insertNewObjectForEntityForName:entityName
inManagedObjectContext:self.backgroundContext];
if (configure) configure(object);
__block NSError *error = nil;
NSManagedObjectID *objectID = [object objectID];
if ([objectID isTemporaryID]) {
[self.backgroundContext obtainPermanentIDsForObjects:@[object] error:&error];
objectID = [object objectID];
}
[self.backgroundContext save:&error];
[self.mainQueueContext performBlockAndWait:^{
[self.mainQueueContext save:&error];
}];
[self.mainQueueContext performBlock:^{
NSError *existingError = nil;
NSManagedObject *managedObject = [self.mainQueueContext existingObjectWithID:objectID
error:&existingError];
if (existingError)
FGOGenericErrorLog(@"Error attempting to fetch object using the object ID. Trying a fetch request.", existingError);
if (!managedObject) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.predicate = [NSPredicate predicateWithFormat:@"SELF == %@", object];
request.fetchLimit = 1;
NSArray *results = [self.mainQueueContext executeFetchRequest:request error:&error];
managedObject = [results count] ? results[0] : nil;
}
if (result) result(managedObject, error);
}];
}];
}
- (void)fetchOrCreateObjectWithRequest:(NSFetchRequest *)request
configure:(void(^)(id object))configure
result:(void(^)(id object, NSError *error))result
{
request.fetchLimit = 1;
[self fetchWithRequest:request completion:^(NSArray *results, NSError *error) {
if (results.count) {
if (result) result(results[0], error);
} else {
[self createObjectOfEntityName:request.entityName configure:configure result:result];
}
}];
}
- (id)backgroundObjectWithID:(NSManagedObjectID *)objectID
{
NSError *error = nil;
NSManagedObject *obj = [self.backgroundContext existingObjectWithID:objectID error:&error];
if (error) FGOGenericErrorLog(@"Error fetching object", error);
return obj;
}
- (id)mainQueueObjectWithID:(NSManagedObjectID *)objectID
{
NSError *error = nil;
NSManagedObject *obj = [self.mainQueueContext existingObjectWithID:objectID error:&error];
if (error) FGOGenericErrorLog(@"Error fetching object", error);
return obj;
}
#pragma mark - Private
- (NSArray *)_transformBackgroundObjectIDsToMainQueueObjects:(NSArray *)objectIDs
{
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[objectIDs count]];
[self.mainQueueContext performBlockAndWait:^{
[objectIDs enumerateObjectsUsingBlock:^(NSManagedObjectID *objectID, NSUInteger idx, BOOL *stop) {
NSError *error = nil;
NSManagedObject *existingObject = [self.mainQueueContext existingObjectWithID:objectID error:&error];
if (error)
FGOGenericErrorLog([NSString stringWithFormat:@"Failed to fetch object with ID %@", objectID], error);
if (existingObject)
[objects addObject:existingObject];
}];
}];
return objects;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment