|
#import "BDKCoreDataStore.h" |
|
|
|
@interface BDKCoreDataStore () |
|
|
|
/** |
|
The managed object context for the application. |
|
*/ |
|
@property (strong, nonatomic) NSManagedObjectContext *mainMOC; |
|
|
|
/** |
|
Stores a reference to the name of the Core Data Model. |
|
*/ |
|
@property (strong, nonatomic) NSString *storeName; |
|
|
|
/** |
|
The managed object model for the application. |
|
*/ |
|
@property (strong, nonatomic) NSManagedObjectModel *mom; |
|
|
|
/** |
|
The persistent store coordinator for the application. |
|
*/ |
|
@property (strong, nonatomic) NSPersistentStoreCoordinator *psc; |
|
|
|
/** |
|
A common Core Data operation queue where all writes go. |
|
*/ |
|
@property (strong, nonatomic) NSOperationQueue *coreDataQueue; |
|
|
|
/** |
|
Initializes and returns a version of this store object with a name that matches the Core Data model |
|
you're using. |
|
|
|
@param name The name of your Core Data managed object model. |
|
@return An instance of this store; you may want to keep it in your AppDelegate. |
|
*/ |
|
- (instancetype)initWithName:(NSString *)name; |
|
|
|
/** |
|
Registers this store object with the NSManagedObjectContextDidSaveNotification. |
|
*/ |
|
- (void)setupSaveNotification; |
|
|
|
@end |
|
|
|
@implementation BDKCoreDataStore |
|
|
|
+ (instancetype)sharedInstance { |
|
static id _sharedInstance = nil; |
|
static dispatch_once_t onceToken; |
|
dispatch_once(&onceToken, ^{ |
|
_sharedInstance = [self storeWithName:@"NameOfMyStore"]; |
|
}); |
|
return _sharedInstance; |
|
} |
|
|
|
+ (NSManagedObjectContext *)mainThreadContext { |
|
return [[self sharedInstance] mainMOC]; |
|
} |
|
|
|
+ (instancetype)storeWithName:(NSString *)name { |
|
return [[self alloc] initWithName:name]; |
|
} |
|
|
|
- (instancetype)initWithName:(NSString *)name { |
|
self = [super init]; |
|
if (!self) return nil; |
|
|
|
_storeName = name; |
|
_coreDataQueue = [NSOperationQueue new]; |
|
[_coreDataQueue setName:@"gr.kree.BDKCoreDataStoreOperationQueue"]; |
|
[_coreDataQueue setMaxConcurrentOperationCount:1]; |
|
|
|
_mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; |
|
[_mainMOC setPersistentStoreCoordinator:self.psc]; |
|
[_mainMOC setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy]; |
|
|
|
[self setupSaveNotification]; |
|
|
|
return self; |
|
} |
|
|
|
#pragma mark - Private properties |
|
|
|
- (NSManagedObjectModel *)mom { |
|
if (_mom) return _mom; |
|
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:self.storeName withExtension:@"momd"]; |
|
_mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; |
|
return _mom; |
|
} |
|
|
|
- (NSPersistentStoreCoordinator *)psc { |
|
if (_psc) return _psc; |
|
_psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.mom]; |
|
NSString *storeFileName = [self.storeName stringByAppendingPathExtension:@"sqlite3"]; |
|
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:storeFileName]; |
|
NSError *error = nil; |
|
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption: @YES, |
|
NSInferMappingModelAutomaticallyOption: @YES}; |
|
if (![_psc addPersistentStoreWithType:NSSQLiteStoreType |
|
configuration:nil |
|
URL:storeURL |
|
options:options |
|
error:&error]) { |
|
// Try blasting the store out, if that doesn't work, fail hard |
|
NSError *innerError = nil; |
|
NSLog(@"Deleting store at URL %@.", storeURL); |
|
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&innerError]; |
|
if (innerError || ![_psc addPersistentStoreWithType:NSSQLiteStoreType |
|
configuration:nil |
|
URL:storeURL |
|
options:options |
|
error:&error]) { |
|
NSLog(@"Unresolved error %@, %@", error, [error userInfo]); |
|
abort(); |
|
} |
|
|
|
} |
|
return _psc; |
|
} |
|
|
|
#pragma mark - Public methods |
|
|
|
- (void)save { |
|
NSError *error = nil; |
|
NSManagedObjectContext *moc = self.mainMOC; |
|
if (!moc) return; |
|
|
|
if ([moc hasChanges]) { |
|
BOOL success = [moc save:&error]; |
|
if (error) { |
|
NSLog(@"Core data error %@, %@.", error, [error userInfo]); |
|
abort(); |
|
} |
|
if (!success) { |
|
NSLog(@"Core data did not save properly."); |
|
} else { |
|
NSLog(@"Just saved NSManagedObjectContext for main thread."); |
|
} |
|
} else { |
|
NSLog(@"Skipped saving NSManagedObjectContext because it didn't have any changes."); |
|
} |
|
} |
|
|
|
#pragma mark - Private methods |
|
|
|
- (void)setupSaveNotification { |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(contextDidSave:) |
|
name:NSManagedObjectContextDidSaveNotification |
|
object:self.mainMOC]; |
|
} |
|
|
|
- (void)contextDidSave:(NSNotification *)notification { |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
NSManagedObjectContext *moc = self.mainMOC; |
|
if (![[notification object] isEqual:moc]) { |
|
[moc performBlock:^{ |
|
NSLog(@"Merging changes from private context %p.", [notification object]); |
|
[moc mergeChangesFromContextDidSaveNotification:notification]; |
|
}]; |
|
} |
|
}); |
|
} |
|
|
|
- (NSManagedObjectContext *)freshPrivateContext { |
|
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] |
|
initWithConcurrencyType:NSPrivateQueueConcurrencyType]; |
|
[context setPersistentStoreCoordinator:self.psc]; |
|
[context setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy]; |
|
// This is default on iOS, but hey |
|
[context setUndoManager:nil]; |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(contextDidSave:) |
|
name:NSManagedObjectContextDidSaveNotification |
|
object:context]; |
|
return context; |
|
} |
|
|
|
- (NSURL *)applicationDocumentsDirectory { |
|
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory |
|
inDomains:NSUserDomainMask] lastObject]; |
|
} |
|
|
|
- (void)cleanupPrivateContext:(NSManagedObjectContext *)context { |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:NSManagedObjectContextDidSaveNotification |
|
object:context]; |
|
context = nil; |
|
} |
|
|
|
@end |