Last active
December 10, 2015 08:38
-
-
Save indragiek/4409438 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// 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 | |
This file contains hidden or 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
// | |
// 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