blob: c3f2ac7a5c43626ca8ffa74d2874bda43b17d510 [file] [log] [blame]
//
// 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, copy, readwrite) NSURL *oldModelURL;
@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
oldModelURL:(NSURL *)oldModelURL
{
NSParameterAssert(storeURL);
NSParameterAssert(modelURL);
SMStoreController *controller = [self new];
controller.oldModelURL = oldModelURL;
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
attemptMigrationWithOldStore:NO
oldModelURL:nil];
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
attemptMigrationWithOldStore:YES
oldModelURL:self.oldModelURL];
if (error) {
return nil;
}
return _persistentStoreCoordinator;
}
- (BOOL)addPersistentStoreToCoordinator:(NSError **)error
attemptMigrationWithOldStore:(BOOL)attemptMigrationWithOldStore
oldModelURL:(NSURL *)oldModelURL
{
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)};
NSError *migrationError;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:options
error:&migrationError]) {
NSError *mappingError;
NSError *moveError = nil;
BOOL migrationSuccess = NO;
NSURL *newStoreURL = nil;
if (attemptMigrationWithOldStore) {
// generate a managed object model for the old schema, we will use it to attempt a migration
// between the old and new schemas
NSManagedObjectModel *oldModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:oldModelURL];
NSMappingModel *mappingModel = [NSMappingModel inferredMappingModelForSourceModel:oldModel
destinationModel:self.managedObjectModel
error:&mappingError];
// attempt a migration if the mapping model exists
if (mappingModel != nil) {
// Create a migration manager to perform the migration.
NSMigrationManager *manager = [[NSMigrationManager alloc]
initWithSourceModel:oldModel
destinationModel:self.managedObjectModel];
NSError *migrationError = nil;
newStoreURL = [self.storeURL URLByAppendingPathExtension:@"foo"];
[[NSFileManager defaultManager] removeItemAtURL:newStoreURL error:nil]; //clear out any existing temporary file
migrationSuccess = [manager migrateStoreFromURL:self.storeURL
type:NSSQLiteStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:newStoreURL
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:&migrationError];
if (migrationSuccess) {
// if the migration succeeds, remove the old sql store and move the new sql store to the old stores location
// on disk
[[NSFileManager defaultManager] removeItemAtURL:self.storeURL error:nil];
[[NSFileManager defaultManager] moveItemAtURL:newStoreURL toURL:self.storeURL error:&moveError];
}
}
}
// if any step in the migration has failed, delete both of the stores
if (moveError != nil || !migrationSuccess) {
[[NSFileManager defaultManager] removeItemAtURL:self.storeURL error:nil];
[[NSFileManager defaultManager] removeItemAtURL:newStoreURL 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