Created
January 30, 2014 04:54
-
-
Save jamiepinkham/8702841 to your computer and use it in GitHub Desktop.
This file contains 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
// | |
// UBCoreDataStack.h | |
// LevelDBMigrator | |
// | |
// Created by Jamie Pinkham on 1/28/14. | |
// Copyright (c) 2014 Jamie Pinkham. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
@class UBCoreDataController; | |
@protocol UBCoreDataControllerDelegate <NSObject> | |
@optional | |
- (BOOL)retryAfterPersistentStoreCreationFailedWithError:(NSError *)error coreDataController:(UBCoreDataController *)stack; | |
@end | |
@interface UBCoreDataController : NSObject | |
+ (instancetype)defaultController; | |
- (instancetype)initWithStoreFilename:(NSString *)filename; | |
- (instancetype)initWithStoreFilename:(NSString *)filename migratesAutomatically:(BOOL)migratesAutomatically; | |
- (instancetype)initWithStoreFilename:(NSString *)filename migratesAutomatically:(BOOL)migratesAutomatically modelName:(NSString *)modelName; | |
- (instancetype)initWithStoreFilename:(NSString *)filename migratesAutomatically:(BOOL)migratesAutomatically modelName:(NSString *)modelName storeType:(NSString *)storeType; | |
- (instancetype)initWithStoreFilename:(NSString *)filename modelName:(NSString *)modelName storeType:(NSString *)storeType storeOptions:(NSDictionary *)storeOptions; | |
- (instancetype)initWithStoreURL:(NSURL *)storeURL storeType:(NSString *)storeType storeOptions:(NSDictionary *)storeOptions modelConfiguration:(NSString *)modelConfiguration modelURL:(NSURL *)modelURL; | |
@property (nonatomic, readonly) NSManagedObjectContext *mainQueueManagedObjectContext; | |
@property (nonatomic, readonly) NSManagedObjectContext *privateQueueManagedObjectContext; | |
- (NSManagedObjectContext *)childContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)type; | |
@property (nonatomic, readonly) NSURL *storeURL; | |
@property (nonatomic, readonly) NSString *storeType; | |
@property (nonatomic, readonly) NSDictionary *storeOptions; | |
@property (nonatomic, readonly) NSString *modelConfiguration; | |
@property (nonatomic, readonly) NSURL *modelURL; | |
@property (nonatomic, weak) id<UBCoreDataControllerDelegate> delegate; | |
@end | |
This file contains 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
// | |
// UBCoreDataStack.m | |
// LevelDBMigrator | |
// | |
// Created by Jamie Pinkham on 1/28/14. | |
// Copyright (c) 2014 Jamie Pinkham. All rights reserved. | |
// | |
#import "UBCoreDataController.h" | |
#import <CoreData/CoreData.h> | |
NSString * UB_defaultDatabaseFilename(void) | |
{ | |
NSBundle *mainBundle = [NSBundle mainBundle]; | |
NSString *appName = [mainBundle objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleIdentifierKey]; | |
NSString *filename = [NSString stringWithFormat:@"%@.sqlite", appName]; | |
return filename; | |
} | |
NSURL * UB_databaseFileWithFilename(NSString *databaseFileName) | |
{ | |
NSError *findApplicationSupportDirectoryError = nil; | |
NSURL *applicationSupportDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&findApplicationSupportDirectoryError]; | |
if (applicationSupportDirectory == nil) | |
{ | |
NSLog(@"Error locating the Application Support directory. %@", findApplicationSupportDirectoryError); | |
return nil; | |
} | |
return [applicationSupportDirectory URLByAppendingPathComponent:databaseFileName]; | |
} | |
NSURL * UB_modelURLWithModelName(NSString *modelName) | |
{ | |
if(modelName) | |
{ | |
NSBundle *mainBundle = [NSBundle mainBundle]; | |
NSURL *url = [mainBundle URLForResource:modelName withExtension:@"momd"]; | |
return url; | |
} | |
else | |
{ | |
return nil; | |
} | |
} | |
@interface UBCoreDataController () | |
@property (nonatomic, strong) NSPersistentStoreCoordinator *mainQueuePersistentStoreCoordinator; | |
@property (nonatomic, strong) NSPersistentStoreCoordinator *privateQueuePersistentStoreCoordinator; | |
@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel; | |
@property (nonatomic, strong) NSManagedObjectContext *privateQueueManagedObjectContext; | |
@property (nonatomic, strong, readwrite) NSManagedObjectContext *mainQueueManagedObjectContext; | |
@end | |
@implementation UBCoreDataController | |
+ (instancetype)defaultController | |
{ | |
static UBCoreDataController *defaultController = nil; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
defaultController = [[[self class] alloc] initWithStoreFilename:UB_defaultDatabaseFilename() migratesAutomatically:YES]; | |
}); | |
return defaultController; | |
} | |
- (id)init | |
{ | |
return [self initWithStoreFilename:UB_defaultDatabaseFilename()]; | |
} | |
- (instancetype) initWithStoreFilename:(NSString *)filename | |
{ | |
return [self initWithStoreFilename:filename migratesAutomatically:NO]; | |
} | |
- (instancetype)initWithStoreFilename:(NSString *)filename migratesAutomatically:(BOOL)migratesAutomatically | |
{ | |
return [self initWithStoreFilename:filename migratesAutomatically:migratesAutomatically modelName:nil]; | |
} | |
- (instancetype)initWithStoreFilename:(NSString *)filename migratesAutomatically:(BOOL)migratesAutomatically modelName:(NSString *)modelName | |
{ | |
return [self initWithStoreFilename:filename migratesAutomatically:migratesAutomatically modelName:modelName storeType:nil]; | |
} | |
- (instancetype)initWithStoreFilename:(NSString *)filename migratesAutomatically:(BOOL)migratesAutomatically modelName:(NSString *)modelName storeType:(NSString *)storeType | |
{ | |
NSDictionary *storeOptions = @{ | |
NSMigratePersistentStoresAutomaticallyOption : @(migratesAutomatically), | |
NSInferMappingModelAutomaticallyOption : @(migratesAutomatically) | |
}; | |
return [self initWithStoreFilename:filename modelName:modelName storeType:storeType storeOptions:storeOptions]; | |
} | |
- (instancetype)initWithStoreFilename:(NSString *)filename modelName:(NSString *)modelName storeType:(NSString *)storeType storeOptions:(NSDictionary *)storeOptions; | |
{ | |
NSURL *storeURL = UB_databaseFileWithFilename(filename); | |
NSURL *modelURL = UB_modelURLWithModelName(modelName); | |
return [self initWithStoreURL:storeURL storeType:storeType storeOptions:storeOptions modelConfiguration:nil modelURL:modelURL]; | |
} | |
- (instancetype)initWithStoreURL:(NSURL *)storeURL storeType:(NSString *)storeType storeOptions:(NSDictionary *)storeOptions modelConfiguration:(NSString *)modelConfiguration modelURL:(NSURL *)modelURL | |
{ | |
if(self = [super init]) | |
{ | |
_storeURL = [storeURL copy]; | |
_storeType = [storeType copy]; | |
if(_storeType == nil) | |
{ | |
_storeType = NSSQLiteStoreType; | |
} | |
_storeOptions = [storeOptions copy]; | |
_modelURL = [modelURL copy]; | |
_modelConfiguration = [modelConfiguration copy]; | |
UIApplication *application = [UIApplication sharedApplication]; | |
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; | |
[defaultCenter addObserver:self selector:@selector(handleDidEnterBackgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:application]; | |
[defaultCenter addObserver:self selector:@selector(handleManagedObjectContextDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil]; | |
} | |
return self; | |
} | |
- (void)dealloc | |
{ | |
UIApplication *application = [UIApplication sharedApplication]; | |
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; | |
[defaultCenter removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:application]; | |
[defaultCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:nil]; | |
} | |
- (NSManagedObjectModel *)managedObjectModel | |
{ | |
if(_managedObjectModel == nil) | |
{ | |
if(self.modelURL) | |
{ | |
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL]; | |
} | |
else | |
{ | |
NSBundle *mainBundle = [NSBundle mainBundle]; | |
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:@[mainBundle]]; | |
} | |
} | |
return _managedObjectModel; | |
} | |
- (NSPersistentStoreCoordinator *)mainQueuePersistentStoreCoordinator | |
{ | |
if(_mainQueuePersistentStoreCoordinator == nil) | |
{ | |
_mainQueuePersistentStoreCoordinator = [self buildPersistentStoreCoordinator]; | |
} | |
return _mainQueuePersistentStoreCoordinator; | |
} | |
- (NSPersistentStoreCoordinator *)privateQueuePersistentStoreCoordinator | |
{ | |
if(_privateQueuePersistentStoreCoordinator == nil) | |
{ | |
_privateQueuePersistentStoreCoordinator = [self buildPersistentStoreCoordinator]; | |
} | |
return _privateQueuePersistentStoreCoordinator; | |
} | |
- (NSManagedObjectContext *)mainQueueManagedObjectContext | |
{ | |
if(_mainQueueManagedObjectContext == nil) | |
{ | |
_mainQueueManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; | |
_mainQueueManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; | |
_mainQueueManagedObjectContext.persistentStoreCoordinator = self.mainQueuePersistentStoreCoordinator; | |
[[_mainQueueManagedObjectContext userInfo] setObject:@"Main Thread" forKey:@"mocIdentifier"]; | |
} | |
return _mainQueueManagedObjectContext; | |
} | |
- (NSManagedObjectContext *)privateQueueManagedObjectContext | |
{ | |
if(_privateQueueManagedObjectContext == nil) | |
{ | |
_privateQueueManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; | |
_privateQueueManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; | |
_privateQueueManagedObjectContext.persistentStoreCoordinator = self.privateQueuePersistentStoreCoordinator; | |
[[_privateQueueManagedObjectContext userInfo] setObject:@"Private Queue" forKey:@"mocIdentifier"]; | |
} | |
return _privateQueueManagedObjectContext; | |
} | |
- (NSManagedObjectContext *)childContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)type | |
{ | |
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:type]; | |
context.parentContext = self.privateQueueManagedObjectContext; | |
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; | |
[context userInfo][@"mocIdentifier"] = @"Child Context"; | |
return context; | |
} | |
- (void)handleManagedObjectContextDidSaveNotification:(NSNotification *)notification | |
{ | |
NSManagedObjectContext *saveContext = [notification object]; | |
NSString *contextIdentifier = [saveContext userInfo][@"mocIdentifier"]; | |
NSLog(@"did save notification from %@", contextIdentifier); | |
if(saveContext == self.mainQueueManagedObjectContext) | |
{ | |
[self.privateQueueManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; | |
[self saveMasterContext]; | |
} | |
else if(saveContext == self.privateQueueManagedObjectContext) | |
{ | |
[self.mainQueueManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; | |
} | |
else if([saveContext parentContext] == self.privateQueueManagedObjectContext) | |
{ | |
[self saveMasterContext]; | |
[self.mainQueueManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; | |
} | |
} | |
- (void)handleDidEnterBackgroundNotification:(NSNotification *)notification | |
{ | |
[self saveMasterContext]; | |
} | |
- (void)saveMasterContext | |
{ | |
if([self.privateQueueManagedObjectContext hasChanges]) | |
{ | |
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"background-moc-save" expirationHandler:^{ | |
NSLog(@"we expired"); | |
}]; | |
if(identifier != UIBackgroundTaskInvalid) | |
{ | |
[self.privateQueueManagedObjectContext performBlock:^{ | |
[self.privateQueueManagedObjectContext save:nil]; | |
NSLog(@"saved background context"); | |
[[UIApplication sharedApplication] endBackgroundTask:identifier]; | |
}]; | |
} | |
} | |
} | |
- (NSPersistentStoreCoordinator *)buildPersistentStoreCoordinator | |
{ | |
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; | |
NSError *error = nil; | |
NSPersistentStore *store = [psc addPersistentStoreWithType:self.storeType configuration:self.modelConfiguration URL:self.storeURL options:self.storeOptions error:&error]; | |
if(!store && [self.delegate respondsToSelector:@selector(retryAfterPersistentStoreCreationFailedWithError:coreDataController:)]) | |
{ | |
BOOL retry = [self.delegate retryAfterPersistentStoreCreationFailedWithError:error coreDataController:self]; | |
if(retry) | |
{ | |
store = [psc addPersistentStoreWithType:self.storeType configuration:self.modelConfiguration URL:self.storeURL options:self.storeOptions error:NULL]; | |
} | |
} | |
return psc; | |
} | |
@end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment