mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-02 22:42:45 +08:00
There are some issues with the current implementation of the RKInMemoryMappingCache that are allowing the creation of duplicated objects. This commit switches the default cacheStrategy to RKFetchRequestMappingCache. The primary key branch (#613) contains architecture improvements that allow for a robust fix to the issues in the memory based cache.
402 lines
17 KiB
Objective-C
402 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 "RKFetchRequestMappingCache.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 = [[RKFetchRequestMappingCache 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
|