Files
RestKit/Code/CoreData/RKManagedObjectStore.m

396 lines
18 KiB
Objective-C

//
// RKManagedObjectStore.m
// RestKit
//
// Created by Blake Watters on 9/22/09.
// Copyright (c) 2009-2012 RestKit. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "RKManagedObjectStore.h"
#import "RKLog.h"
#import "RKSearchWordObserver.h"
#import "RKPropertyInspector.h"
#import "RKPropertyInspector+CoreData.h"
#import "RKAlert.h"
#import "RKDirectory.h"
#import "RKInMemoryManagedObjectCache.h"
#import "RKFetchRequestManagedObjectCache.h"
#import "NSBundle+RKAdditions.h"
#import "NSManagedObjectContext+RKAdditions.h"
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitCoreData
NSString * const RKManagedObjectStoreDidFailSaveNotification = @"RKManagedObjectStoreDidFailSaveNotification";
static RKManagedObjectStore *defaultStore = nil;
@interface RKManagedObjectStore ()
@property (nonatomic, retain, readwrite) NSManagedObjectContext *primaryManagedObjectContext;
@property (nonatomic, retain, readwrite) NSManagedObjectContext *mainQueueManagedObjectContext;
- (id)initWithStoreFilename:(NSString *)storeFilename inDirectory:(NSString *)nilOrDirectoryPath usingSeedDatabaseName:(NSString *)nilOrNameOfSeedDatabaseInMainBundle managedObjectModel:(NSManagedObjectModel *)nilOrManagedObjectModel delegate:(id)delegate;
- (void)createPersistentStoreCoordinator;
- (void)createStoreIfNecessaryUsingSeedDatabase:(NSString *)seedDatabase;
@end
@implementation RKManagedObjectStore
@synthesize delegate = _delegate;
@synthesize storeFilename = _storeFilename;
@synthesize pathToStoreFile = _pathToStoreFile;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
@synthesize cacheStrategy = _cacheStrategy;
@synthesize primaryManagedObjectContext = _primaryManagedObjectContext;
@synthesize mainQueueManagedObjectContext = _mainQueueManagedObjectContext;
+ (RKManagedObjectStore *)defaultStore
{
return defaultStore;
}
+ (void)setDefaultStore:(RKManagedObjectStore *)managedObjectStore
{
[managedObjectStore retain];
[defaultStore release];
defaultStore = managedObjectStore;
}
+ (void)deleteStoreAtPath:(NSString *)path
{
NSURL *storeURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:storeURL.path]) {
if (! [[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error]) {
NSAssert(NO, @"Managed object store failed to delete persistent store : %@", error);
}
} else {
RKLogWarning(@"Asked to delete persistent store but no store file exists at path: %@", storeURL.path);
}
}
+ (void)deleteStoreInApplicationDataDirectoryWithFilename:(NSString *)filename
{
NSString *path = [[RKDirectory applicationDataDirectory] stringByAppendingPathComponent:filename];
[self deleteStoreAtPath:path];
}
+ (RKManagedObjectStore *)objectStoreWithStoreFilename:(NSString *)storeFilename
{
return [self objectStoreWithStoreFilename:storeFilename usingSeedDatabaseName:nil managedObjectModel:nil delegate:nil];
}
+ (RKManagedObjectStore *)objectStoreWithStoreFilename:(NSString *)storeFilename usingSeedDatabaseName:(NSString *)nilOrNameOfSeedDatabaseInMainBundle managedObjectModel:(NSManagedObjectModel *)nilOrManagedObjectModel delegate:(id)delegate
{
return [[[self alloc] initWithStoreFilename:storeFilename inDirectory:nil usingSeedDatabaseName:nilOrNameOfSeedDatabaseInMainBundle managedObjectModel:nilOrManagedObjectModel delegate:delegate] autorelease];
}
+ (RKManagedObjectStore *)objectStoreWithStoreFilename:(NSString *)storeFilename inDirectory:(NSString *)directory usingSeedDatabaseName:(NSString *)nilOrNameOfSeedDatabaseInMainBundle managedObjectModel:(NSManagedObjectModel *)nilOrManagedObjectModel delegate:(id)delegate
{
return [[[self alloc] initWithStoreFilename:storeFilename inDirectory:directory usingSeedDatabaseName:nilOrNameOfSeedDatabaseInMainBundle managedObjectModel:nilOrManagedObjectModel delegate:delegate] autorelease];
}
- (id)initWithStoreFilename:(NSString *)storeFilename
{
return [self initWithStoreFilename:storeFilename inDirectory:nil usingSeedDatabaseName:nil managedObjectModel:nil delegate:nil];
}
- (id)initWithStoreFilename:(NSString *)storeFilename inDirectory:(NSString *)nilOrDirectoryPath usingSeedDatabaseName:(NSString *)nilOrNameOfSeedDatabaseInMainBundle managedObjectModel:(NSManagedObjectModel *)nilOrManagedObjectModel delegate:(id)delegate
{
self = [self init];
if (self) {
_storeFilename = [storeFilename retain];
if (nilOrDirectoryPath == nil) {
// If initializing into Application Data directory, ensure the directory exists
nilOrDirectoryPath = [RKDirectory applicationDataDirectory];
[RKDirectory ensureDirectoryExistsAtPath:nilOrDirectoryPath error:nil];
} else {
// If path given, caller is responsible for directory's existence
BOOL isDir;
NSAssert1([[NSFileManager defaultManager] fileExistsAtPath:nilOrDirectoryPath isDirectory:&isDir] && isDir == YES, @"Specified storage directory exists", nilOrDirectoryPath);
}
_pathToStoreFile = [[nilOrDirectoryPath stringByAppendingPathComponent:_storeFilename] retain];
if (nilOrManagedObjectModel == nil) {
// NOTE: allBundles permits Core Data setup in unit tests
nilOrManagedObjectModel = [NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]];
}
NSMutableArray *allManagedObjectModels = [NSMutableArray arrayWithObject:nilOrManagedObjectModel];
_managedObjectModel = [[NSManagedObjectModel modelByMergingModels:allManagedObjectModels] retain];
_delegate = delegate;
if (nilOrNameOfSeedDatabaseInMainBundle) {
[self createStoreIfNecessaryUsingSeedDatabase:nilOrNameOfSeedDatabaseInMainBundle];
}
[self createPersistentStoreCoordinator];
[self createManagedObjectContexts];
_cacheStrategy = [RKInMemoryManagedObjectCache new];
// Ensure there is a search word observer
[RKSearchWordObserver sharedObserver];
// Hydrate the defaultObjectStore
if (! defaultStore) {
[RKManagedObjectStore setDefaultStore:self];
}
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_storeFilename release];
_storeFilename = nil;
[_pathToStoreFile release];
_pathToStoreFile = nil;
[_managedObjectModel release];
_managedObjectModel = nil;
[_persistentStoreCoordinator release];
_persistentStoreCoordinator = nil;
[_cacheStrategy release];
_cacheStrategy = nil;
[_primaryManagedObjectContext release];
_primaryManagedObjectContext = nil;
[super dealloc];
}
- (BOOL)save:(NSError **)error
{
__block NSError *localError = nil;
__block BOOL success;
[self.primaryManagedObjectContext performBlockAndWait:^{
@try {
success = [self.primaryManagedObjectContext save:&localError];
if (!success) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(managedObjectStore:didFailToSaveContext:error:exception:)]) {
[self.delegate managedObjectStore:self didFailToSaveContext:self.primaryManagedObjectContext error:localError exception:nil];
}
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:localError forKey:@"error"];
[[NSNotificationCenter defaultCenter] postNotificationName:RKManagedObjectStoreDidFailSaveNotification object:self userInfo:userInfo];
if ([[localError domain] isEqualToString:@"NSCocoaErrorDomain"]) {
NSDictionary *userInfo = [localError userInfo];
NSArray *errors = [userInfo valueForKey:@"NSDetailedErrors"];
if (errors) {
for (NSError *detailedError in errors) {
NSDictionary *subUserInfo = [detailedError userInfo];
RKLogError(@"Core Data Save Error\n \
NSLocalizedDescription:\t\t%@\n \
NSValidationErrorKey:\t\t\t%@\n \
NSValidationErrorPredicate:\t%@\n \
NSValidationErrorObject:\n%@\n",
[subUserInfo valueForKey:@"NSLocalizedDescription"],
[subUserInfo valueForKey:@"NSValidationErrorKey"],
[subUserInfo valueForKey:@"NSValidationErrorPredicate"],
[subUserInfo valueForKey:@"NSValidationErrorObject"]);
}
}
else {
RKLogError(@"Core Data Save Error\n \
NSLocalizedDescription:\t\t%@\n \
NSValidationErrorKey:\t\t\t%@\n \
NSValidationErrorPredicate:\t%@\n \
NSValidationErrorObject:\n%@\n",
[userInfo valueForKey:@"NSLocalizedDescription"],
[userInfo valueForKey:@"NSValidationErrorKey"],
[userInfo valueForKey:@"NSValidationErrorPredicate"],
[userInfo valueForKey:@"NSValidationErrorObject"]);
}
}
if (error) {
*error = localError;
}
}
}
@catch (NSException *e) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(managedObjectStore:didFailToSaveContext:error:exception:)]) {
[self.delegate managedObjectStore:self didFailToSaveContext:self.primaryManagedObjectContext error:nil exception:e];
}
else {
@throw;
}
}
}];
return success;
}
- (NSManagedObjectContext *)newChildManagedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType
{
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:concurrencyType];
[managedObjectContext performBlockAndWait:^{
managedObjectContext.parentContext = self.primaryManagedObjectContext;
managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
managedObjectContext.managedObjectStore = self;
}];
return managedObjectContext;
}
- (void)createStoreIfNecessaryUsingSeedDatabase:(NSString *)seedDatabase
{
if (NO == [[NSFileManager defaultManager] fileExistsAtPath:self.pathToStoreFile]) {
NSString *seedDatabasePath = [[NSBundle mainBundle] pathForResource:seedDatabase ofType:nil];
NSAssert1(seedDatabasePath, @"Unable to find seed database file '%@' in the Main Bundle, aborting...", seedDatabase);
RKLogInfo(@"No existing database found, copying from seed path '%@'", seedDatabasePath);
NSError *error;
if (![[NSFileManager defaultManager] copyItemAtPath:seedDatabasePath toPath:self.pathToStoreFile error:&error]) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(managedObjectStore:didFailToCopySeedDatabase:error:)]) {
[self.delegate managedObjectStore:self didFailToCopySeedDatabase:seedDatabase error:error];
} else {
RKLogError(@"Encountered an error during seed database copy: %@", [error localizedDescription]);
}
}
NSAssert1([[NSFileManager defaultManager] fileExistsAtPath:seedDatabasePath], @"Seed database not found at path '%@'!", seedDatabasePath);
}
}
- (void)createPersistentStoreCoordinator
{
NSAssert(_managedObjectModel, @"Cannot create persistent store coordinator without a managed object model");
NSAssert(!_persistentStoreCoordinator, @"Cannot create persistent store coordinator: one already exists.");
NSURL *storeURL = [NSURL fileURLWithPath:self.pathToStoreFile];
NSError *error;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];
// Allow inferred migration from the original version of the application.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(managedObjectStore:didFailToCreatePersistentStoreCoordinatorWithError:)]) {
[self.delegate managedObjectStore:self didFailToCreatePersistentStoreCoordinatorWithError:error];
} else {
NSAssert(NO, @"Managed object store failed to create persistent store coordinator: %@", error);
}
}
}
- (void)createManagedObjectContexts
{
// Our primary MOC is a private queue concurrency type
self.primaryManagedObjectContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
self.primaryManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
self.primaryManagedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
self.primaryManagedObjectContext.managedObjectStore = self;
// Create an MOC for use on the main queue
self.mainQueueManagedObjectContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
self.mainQueueManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
self.mainQueueManagedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
self.mainQueueManagedObjectContext.managedObjectStore = self;
// Merge changes from a primary MOC back into the main queue when complete
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handlePrimaryManagedObjectContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:self.primaryManagedObjectContext];
}
- (void)deletePersistentStoreUsingSeedDatabaseName:(NSString *)seedFile
{
NSURL* storeURL = [NSURL fileURLWithPath:self.pathToStoreFile];
NSError* error = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:storeURL.path]) {
if (![[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error]) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(managedObjectStore:didFailToDeletePersistentStore:error:)]) {
[self.delegate managedObjectStore:self didFailToDeletePersistentStore:self.pathToStoreFile error:error];
}
else {
NSAssert(NO, @"Managed object store failed to delete persistent store : %@", error);
}
}
} else {
RKLogWarning(@"Asked to delete persistent store but no store file exists at path: %@", storeURL.path);
}
[_persistentStoreCoordinator release];
_persistentStoreCoordinator = nil;
if (seedFile) {
[self createStoreIfNecessaryUsingSeedDatabase:seedFile];
}
[self createPersistentStoreCoordinator];
[self createManagedObjectContexts];
}
- (void)deletePersistentStore
{
[self deletePersistentStoreUsingSeedDatabaseName:nil];
}
- (void)handlePrimaryManagedObjectContextDidSaveNotification:(NSNotification *)notification
{
RKLogDebug(@"primaryManagedObjectContext was saved: merging changes to mainQueueManagedObjectContext");
RKLogTrace(@"Merging changes detailed in userInfo dictionary: %@", [notification userInfo]);
[self.mainQueueManagedObjectContext performBlock:^{
[self.mainQueueManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
#pragma mark -
#pragma mark Helpers
- (NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID
{
NSAssert(objectID, @"Cannot fetch a managedObject with a nil objectID");
return [[self managedObjectContextForCurrentThread] objectWithID:objectID];
}
- (NSArray *)objectsWithIDs:(NSArray *)objectIDs
{
NSMutableArray *objects = [[NSMutableArray alloc] init];
for (NSManagedObjectID *objectID in objectIDs) {
[objects addObject:[self objectWithID:objectID]];
}
NSArray *objectArray = [NSArray arrayWithArray:objects];
[objects release];
return objectArray;
}
@end
@implementation RKManagedObjectStore (Deprecations)
+ (RKManagedObjectStore *)defaultObjectStore DEPRECATED_ATTRIBUTE
{
return [RKManagedObjectStore defaultStore];
}
+ (void)setDefaultObjectStore:(RKManagedObjectStore *)objectStore DEPRECATED_ATTRIBUTE
{
[RKManagedObjectStore setDefaultStore:objectStore];
}
@end