blob: 60ce506920ea8a8804ce305b37a5c2baafc6d01d [file] [log] [blame] [edit]
//
// SMStoreController.m
// StoreMad
//
// Created by Andrew Smith on 7/20/12.
// Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "SMStoreController.h"
#import <UIKit/UIApplication.h>
#import "NSManagedObjectContext+StoreMad.h"
NSString * const kSMStoreControllerDidReset = @"kSMStoreControllerDidReset";
@interface SMStoreController ()
@property (nonatomic, copy, readwrite) NSURL *storeURL;
@property (nonatomic, copy, readwrite) NSURL *modelURL;
@property (nonatomic, strong, readwrite) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong, readwrite) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readwrite) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@end
@implementation SMStoreController
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
+ (SMStoreController *)storeControllerWithStoreURL:(NSURL *)storeURL
andModelURL:(NSURL *)modelURL
{
NSParameterAssert(storeURL);
NSParameterAssert(modelURL);
SMStoreController *controller = [self new];
controller.storeURL = storeURL;
controller.modelURL = modelURL;
// Force lazy load
[controller managedObjectContext];
return controller;
}
#pragma mark
- (void)reset
{
@synchronized(self) {
// Nil local variables
_managedObjectContext = nil;
_managedObjectModel = nil;
_persistentStoreCoordinator = nil;
// Delete SQlite
[self deleteStore];
// Rebuild
[self managedObjectContext];
}
}
- (void)deleteStore
{
@synchronized(self) {
// Ensure we are on the main thread
NSAssert([[NSThread currentThread] isEqual:[NSThread mainThread]], @"Delete operation must occur on the main thread");
NSFileManager *fileManager = [NSFileManager defaultManager];
if (self.storeURL) {
[fileManager removeItemAtURL:self.storeURL error:NULL];
}
}
}
- (BOOL)resetStore:(NSError **)error
{
// Ensure we are on the main thread
NSAssert([[NSThread currentThread] isEqual:[NSThread mainThread]],
@"Resetting the store must occur on the main thread");
NSError *internalError;
if (error != NULL) {
*error = internalError;
}
//
// Reset the managed object context
//
[_managedObjectContext reset];
//
// Remove all persistent stores
//
for (NSPersistentStore *store in _persistentStoreCoordinator.persistentStores) {
[_persistentStoreCoordinator removePersistentStore:store
error:&internalError];
if (internalError != nil) {
return NO;
}
}
//
// Delete the underlying SQLite store
//
if ([[NSFileManager defaultManager] fileExistsAtPath:self.storeURL.path]) {
[[NSFileManager defaultManager] removeItemAtURL:self.storeURL
error:&internalError];
}
if (internalError != nil) {
return NO;
}
//
// Rebuild store
//
[self addPersistentStoreToCoordinator:&internalError
deleteSQLiteOnMigrationError:YES];
if (internalError != nil) {
return NO;
}
//
// Send update that the store was just reset, and listeners should purge any NSManagedObjects
// they are holding onto.
//
[[NSNotificationCenter defaultCenter] postNotificationName:kSMStoreControllerDidReset
object:self];
return YES;
}
#pragma mark -
- (void)setSaveOnAppStateChange:(BOOL)saveOnAppStateChange
{
_saveOnAppStateChange = saveOnAppStateChange;
if (!saveOnAppStateChange) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillTerminateNotification
object:nil];
} else {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(saveContext)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(saveContext)
name:UIApplicationWillTerminateNotification
object:nil];
}
}
#pragma mark - CoreData Stack
- (void)saveContext
{
[self.managedObjectContext stm_queueBlockSaveAndWait];
}
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel) {
return _managedObjectModel;
}
NSAssert(_modelURL, @"ModelURL was nil! Could not find resource");
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
[self addPersistentStoreToCoordinator:&error
deleteSQLiteOnMigrationError:YES];
if (error) {
return nil;
}
return _persistentStoreCoordinator;
}
- (BOOL)addPersistentStoreToCoordinator:(NSError **)error
deleteSQLiteOnMigrationError:(BOOL)deleteSQLite
{
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:nil
error:error])
{
/*
Replace this implementation with code to handle the error appropriately.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path.
Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(@"The model used to open the store is incompatable with the one used to create the store! Performing lightweight migration.");
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : @(YES),
NSInferMappingModelAutomaticallyOption : @(YES)};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:options
error:error]) {
//
// This deletes the store at the given URL if there is a migration error. This should
// allow you to add a persistent store again, no matter what migration errors you have.
//
if (deleteSQLite
&& [[NSFileManager defaultManager] fileExistsAtPath:self.storeURL.path]) {
[[NSFileManager defaultManager] removeItemAtURL:self.storeURL error:nil];
}
//
// If you still can't add a persistent store after all this, throw an error.
//
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:options
error:error]) {
return NO;
}
}
}
return YES;
}
@end