mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-01-12 22:51:50 +08:00
401 lines
17 KiB
Objective-C
401 lines
17 KiB
Objective-C
//
|
|
// RKManagedObjectStore.m
|
|
// RestKit
|
|
//
|
|
// Created by Blake Watters on 9/22/09.
|
|
// Copyright 2009 RestKit
|
|
//
|
|
// 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 "NSManagedObject+ActiveRecord.h"
|
|
#import "RKLog.h"
|
|
#import "RKSearchWordObserver.h"
|
|
#import "RKObjectPropertyInspector.h"
|
|
#import "RKObjectPropertyInspector+CoreData.h"
|
|
#import "RKAlert.h"
|
|
#import "RKDirectory.h"
|
|
#import "RKInMemoryMappingCache.h"
|
|
#import "NSBundle+RKAdditions.h"
|
|
#import "NSManagedObjectContext+RKAdditions.h"
|
|
|
|
// Set Logging Component
|
|
#undef RKLogComponent
|
|
#define RKLogComponent lcl_cRestKitCoreData
|
|
|
|
NSString* const RKManagedObjectStoreDidFailSaveNotification = @"RKManagedObjectStoreDidFailSaveNotification";
|
|
static NSString* const RKManagedObjectStoreThreadDictionaryContextKey = @"RKManagedObjectStoreThreadDictionaryContextKey";
|
|
static NSString* const RKManagedObjectStoreThreadDictionaryEntityCacheKey = @"RKManagedObjectStoreThreadDictionaryEntityCacheKey";
|
|
|
|
static RKManagedObjectStore *defaultObjectStore = nil;
|
|
|
|
@interface RKManagedObjectStore ()
|
|
@property (nonatomic, retain, readwrite) NSManagedObjectContext *primaryManagedObjectContext;
|
|
|
|
- (id)initWithStoreFilename:(NSString *)storeFilename inDirectory:(NSString *)nilOrDirectoryPath usingSeedDatabaseName:(NSString *)nilOrNameOfSeedDatabaseInMainBundle managedObjectModel:(NSManagedObjectModel*)nilOrManagedObjectModel delegate:(id)delegate;
|
|
- (void)createPersistentStoreCoordinator;
|
|
- (void)createStoreIfNecessaryUsingSeedDatabase:(NSString*)seedDatabase;
|
|
- (NSManagedObjectContext*)newManagedObjectContext;
|
|
@end
|
|
|
|
@implementation RKManagedObjectStore
|
|
|
|
@synthesize delegate = _delegate;
|
|
@synthesize storeFilename = _storeFilename;
|
|
@synthesize pathToStoreFile = _pathToStoreFile;
|
|
@synthesize managedObjectModel = _managedObjectModel;
|
|
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
|
|
@synthesize cacheStrategy = _cacheStrategy;
|
|
@synthesize primaryManagedObjectContext;
|
|
|
|
+ (RKManagedObjectStore *)defaultObjectStore {
|
|
return defaultObjectStore;
|
|
}
|
|
|
|
+ (void)setDefaultObjectStore:(RKManagedObjectStore *)objectStore {
|
|
[objectStore retain];
|
|
[defaultObjectStore release];
|
|
defaultObjectStore = objectStore;
|
|
|
|
[NSManagedObjectContext setDefaultContext:objectStore.primaryManagedObjectContext];
|
|
}
|
|
|
|
+ (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) {
|
|
nilOrDirectoryPath = [RKDirectory applicationDataDirectory];
|
|
} else {
|
|
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];
|
|
|
|
if (nilOrNameOfSeedDatabaseInMainBundle) {
|
|
[self createStoreIfNecessaryUsingSeedDatabase:nilOrNameOfSeedDatabaseInMainBundle];
|
|
}
|
|
|
|
_delegate = delegate;
|
|
|
|
[self createPersistentStoreCoordinator];
|
|
self.primaryManagedObjectContext = [self newManagedObjectContext];
|
|
|
|
|
|
_cacheStrategy = [[RKInMemoryMappingCache alloc] init];
|
|
|
|
// Ensure there is a search word observer
|
|
[RKSearchWordObserver sharedObserver];
|
|
|
|
// Hydrate the defaultObjectStore
|
|
if (! defaultObjectStore) {
|
|
[RKManagedObjectStore setDefaultObjectStore:self];
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)setThreadLocalObject:(id)value forKey:(id)key {
|
|
NSMutableDictionary* threadDictionary = [[NSThread currentThread] threadDictionary];
|
|
NSString *objectStoreKey = [NSString stringWithFormat:@"RKManagedObjectStore_%p", self];
|
|
if (! [threadDictionary valueForKey:objectStoreKey]) {
|
|
[threadDictionary setValue:[NSMutableDictionary dictionary] forKey:objectStoreKey];
|
|
}
|
|
|
|
[[threadDictionary objectForKey:objectStoreKey] setObject:value forKey:key];
|
|
}
|
|
|
|
- (id)threadLocalObjectForKey:(id)key {
|
|
NSMutableDictionary* threadDictionary = [[NSThread currentThread] threadDictionary];
|
|
NSString *objectStoreKey = [NSString stringWithFormat:@"RKManagedObjectStore_%p", self];
|
|
if (! [threadDictionary valueForKey:objectStoreKey]) {
|
|
[threadDictionary setObject:[NSMutableDictionary dictionary] forKey:objectStoreKey];
|
|
}
|
|
|
|
return [[threadDictionary objectForKey:objectStoreKey] objectForKey:key];
|
|
}
|
|
|
|
- (void)removeThreadLocalObjectForKey:(id)key {
|
|
NSMutableDictionary* threadDictionary = [[NSThread currentThread] threadDictionary];
|
|
NSString *objectStoreKey = [NSString stringWithFormat:@"RKManagedObjectStore_%p", self];
|
|
if (! [threadDictionary valueForKey:objectStoreKey]) {
|
|
[threadDictionary setObject:[NSMutableDictionary dictionary] forKey:objectStoreKey];
|
|
}
|
|
|
|
[[threadDictionary objectForKey:objectStoreKey] removeObjectForKey:key];
|
|
}
|
|
|
|
- (void)clearThreadLocalStorage {
|
|
// Clear out our Thread local information
|
|
NSManagedObjectContext *managedObjectContext = [self threadLocalObjectForKey:RKManagedObjectStoreThreadDictionaryContextKey];
|
|
if (managedObjectContext) {
|
|
[self removeThreadLocalObjectForKey:RKManagedObjectStoreThreadDictionaryContextKey];
|
|
}
|
|
if ([self threadLocalObjectForKey:RKManagedObjectStoreThreadDictionaryEntityCacheKey]) {
|
|
[self removeThreadLocalObjectForKey:RKManagedObjectStoreThreadDictionaryEntityCacheKey];
|
|
}
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
[self clearThreadLocalStorage];
|
|
|
|
[_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];
|
|
}
|
|
|
|
/**
|
|
Performs the save action for the application, which is to send the save:
|
|
message to the application's managed object context.
|
|
*/
|
|
- (BOOL)save:(NSError **)error {
|
|
NSManagedObjectContext* moc = [self managedObjectContextForCurrentThread];
|
|
NSError *localError = nil;
|
|
|
|
@try {
|
|
if (![moc save:&localError]) {
|
|
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(managedObjectStore:didFailToSaveContext:error:exception:)]) {
|
|
[self.delegate managedObjectStore:self didFailToSaveContext:moc 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;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
}
|
|
@catch (NSException* e) {
|
|
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(managedObjectStore:didFailToSaveContext:error:exception:)]) {
|
|
[self.delegate managedObjectStore:self didFailToSaveContext:moc error:nil exception:e];
|
|
}
|
|
else {
|
|
@throw;
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (NSManagedObjectContext *)newManagedObjectContext {
|
|
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] init];
|
|
[managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
|
|
[managedObjectContext setUndoManager:nil];
|
|
[managedObjectContext setMergePolicy: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)deletePersistantStoreUsingSeedDatabaseName:(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];
|
|
}
|
|
|
|
- (void)deletePersistantStore {
|
|
[self deletePersistantStoreUsingSeedDatabaseName:nil];
|
|
|
|
// Recreate the MOC
|
|
self.primaryManagedObjectContext = [self newManagedObjectContext];
|
|
}
|
|
|
|
- (NSManagedObjectContext *)managedObjectContextForCurrentThread {
|
|
if ([NSThread isMainThread]) {
|
|
return self.primaryManagedObjectContext;
|
|
}
|
|
|
|
// Background threads leverage thread-local storage
|
|
NSManagedObjectContext* managedObjectContext = [self threadLocalObjectForKey:RKManagedObjectStoreThreadDictionaryContextKey];
|
|
if (!managedObjectContext) {
|
|
managedObjectContext = [self newManagedObjectContext];
|
|
|
|
// Store into thread local storage dictionary
|
|
[self setThreadLocalObject:managedObjectContext forKey:RKManagedObjectStoreThreadDictionaryContextKey];
|
|
[managedObjectContext release];
|
|
|
|
// If we are a background Thread MOC, we need to inform the main thread on save
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(mergeChanges:)
|
|
name:NSManagedObjectContextDidSaveNotification
|
|
object:managedObjectContext];
|
|
}
|
|
|
|
return managedObjectContext;
|
|
}
|
|
|
|
- (void)mergeChangesOnMainThreadWithNotification:(NSNotification*)notification {
|
|
assert([NSThread isMainThread]);
|
|
[self.primaryManagedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
|
|
withObject:notification
|
|
waitUntilDone:YES];
|
|
}
|
|
|
|
- (void)mergeChanges:(NSNotification *)notification {
|
|
// Merge changes into the main context on the main thread
|
|
[self performSelectorOnMainThread:@selector(mergeChangesOnMainThreadWithNotification:) withObject:notification waitUntilDone:YES];
|
|
}
|
|
|
|
#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
|