Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions source/CoreDataStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ typedef enum CDSStoreType
CDSStoreTypeInMemory
} CDSStoreType;

@class CoreDataStack;

typedef void (^CoreDataStackErrorHandler)(CoreDataStack* stack, NSError* error);

@interface CoreDataStack : NSObject
{
NSManagedObjectModel* _mom;
Expand Down Expand Up @@ -85,6 +89,9 @@ typedef enum CDSStoreType
instead of silently failing and you not noticing that your in-memory data is corrupt! */
@property(nonatomic) BOOL shouldAssertWhenSaveFails;

/** add a handler block for errors that could happen in persistent store creation or updating. Called whenever no other error handling block is explicitly passed as parameter to any of this classes methods */
@property(nonatomic) CoreDataStackErrorHandler defaultErrorBlock;

/*! To use CoreData, you need to provide a permanent filename for it to save its sqlite DB to disk - or provide a manual URL to where
you've already saved it. You also need to provide a model name

Expand Down Expand Up @@ -287,4 +294,15 @@ typedef enum CDSStoreType
*/
-(void) wipeAllData;

/** remove current database / persistent store and replace with a new one. A backup of the old database will be saved at the same position with appended extension .old
@param newDatabaseURL the NSURL of the new database / persistent store, pass nil if you want to reset the current persistent store without loading a new database.
*/
-(void)replaceDatabaseWithURL:(NSURL*)newDatabaseURL;

/** resets the current database / persistent store. A backup of the old database will be saved at the same position with appended extension .old
Convinience method for calling -replaceDatabaseWithURL:nil
@see replaceDatabaseWithURL:
*/
-(void)resetDatabase;

@end
109 changes: 87 additions & 22 deletions source/CoreDataStack.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ @implementation CoreDataStack
@synthesize modelName;
@synthesize coreDataStoreType;
@synthesize automaticallyMigratePreviousCoreData;
@synthesize defaultErrorBlock;

#pragma mark - main class

Expand Down Expand Up @@ -288,11 +289,12 @@ -(NSPersistentStoreCoordinator*) persistentStoreCoordinator
NSLog(@"[%@] Warning: not migrating store (Apple default, but it's incorrect for 99%% of projects)", [self class]);

if (![_psc addPersistentStoreWithType:storeType configuration:nil URL:self.databaseURL options:options error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
*/
NSLog(@"[%@] Unresolved error %@, %@", [self class], error, [error userInfo]);
abort();
if (self.defaultErrorBlock) {
self.defaultErrorBlock(self, error);
} else {
NSLog(@"[%@] Unresolved error %@, %@", [self class], error, [error userInfo]);
abort();
}
}
}

Expand Down Expand Up @@ -341,7 +343,7 @@ -(NSManagedObject*) fetchOneOrNil:(Class) c predicate:(NSPredicate*) predicate e
NSFetchRequest* fetch = [self fetchRequestForEntity:c];
fetch.predicate = predicate;
NSArray* result = [self.managedObjectContext executeFetchRequest:fetch error:error];
if( result == nil )
{
return nil;
Expand All @@ -363,8 +365,9 @@ -(NSManagedObject*) fetchOneOrNil:(Class) c predicate:(NSPredicate*) predicate e
-(BOOL) storeContainsAtLeastOneEntityOfClass:(Class) c
{
NSFetchRequest* fetchAny = [self fetchRequestForEntity:c];
NSArray* anyCats = [self.managedObjectContext executeFetchRequest:fetchAny error:nil];

NSError* error;
NSArray* anyCats = [self.managedObjectContext executeFetchRequest:fetchAny error:&error];
if (error && self.defaultErrorBlock) defaultErrorBlock(self, error);
if( [anyCats count] > 0 )
return TRUE;

Expand All @@ -388,6 +391,7 @@ -(NSArray*) fetchEntities:(Class) c matchingPredicate:(NSPredicate*) predicate s

if( result == nil )
{
if (error && self.defaultErrorBlock) defaultErrorBlock(self, error);
NSLog(@"[%@] ERROR calling fetchEntities:matchingPredicate for predicate %@, error = %@", [self class], predicate, error );
return nil;
}
Expand All @@ -405,10 +409,11 @@ -(int) countEntities:(Class) c matchingPredicate:(NSPredicate*) predicate
if( result == nil )
{
NSLog(@"[%@] ERROR calling countEntities:matchingPredicate for predicate %@, error = %@", [self class], predicate, error );
if (error && self.defaultErrorBlock) defaultErrorBlock(self, error);
return -1;
}
else
return result.count;
return (int)result.count;
}

-(void) saveOrFail:(void(^)(NSError* errorOrNil)) blockFailedToSave
Expand All @@ -420,18 +425,33 @@ -(void) saveOrFail:(void(^)(NSError* errorOrNil)) blockFailedToSave
}
else
{
if( [self managedObjectContext] == nil )
{
blockFailedToSave( [NSError errorWithDomain:@"CoreDataStack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Attempted to save a nil NSManagedObjectContext. This CoreDataStack has no context - probably there was an earlier error trying to access the CoreData database file", NSLocalizedDescriptionKey, nil]] );
}
else
{
blockFailedToSave( error );

if( self.shouldAssertWhenSaveFails )
NSAssert( FALSE, @"A CoreData save failed, and you asked me to Assert when this happens. This is a very serious error - you should investigate!");
}
}
if (!blockFailedToSave && self.defaultErrorBlock) {
if( [self managedObjectContext] == nil )
{
self.defaultErrorBlock(self, [NSError errorWithDomain:@"CoreDataStack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Attempted to save a nil NSManagedObjectContext. This CoreDataStack has no context - probably there was an earlier error trying to access the CoreData database file", NSLocalizedDescriptionKey, nil]]);
}
else
{
self.defaultErrorBlock(self, error);

if( self.shouldAssertWhenSaveFails )
NSAssert( FALSE, @"A CoreData save failed, and you asked me to Assert when this happens. This is a very serious error - you should investigate!");
}

} else {
if( [self managedObjectContext] == nil )
{
blockFailedToSave( [NSError errorWithDomain:@"CoreDataStack" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Attempted to save a nil NSManagedObjectContext. This CoreDataStack has no context - probably there was an earlier error trying to access the CoreData database file", NSLocalizedDescriptionKey, nil]] );
}
else
{
blockFailedToSave( error );

if( self.shouldAssertWhenSaveFails )
NSAssert( FALSE, @"A CoreData save failed, and you asked me to Assert when this happens. This is a very serious error - you should investigate!");
}
}
}
}

-(void) wipeAllData
Expand All @@ -445,10 +465,55 @@ -(void) wipeAllData
_moc = nil;
_mom = nil;
_psc = nil;


if (error && self.defaultErrorBlock) defaultErrorBlock(self, error);

/** ... side effect: all NSFetchedResultsController's will now explode because Apple didn't code them very well */
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDestroyAllNSFetchedResultsControllers object:self];
}
}

-(void)replaceDatabaseWithURL:(NSURL *)newDatabaseURL {
[_moc reset];
NSError *error;
for (NSPersistentStore *store in _psc.persistentStores) {
BOOL removed = [_psc removePersistentStore:store error:&error];
if (!removed) {
NSLog(@"Unable to remove persistent store: %@", error);
}
}
if (newDatabaseURL) {
BOOL moved = [[NSFileManager defaultManager] replaceItemAtURL:self.databaseURL
withItemAtURL:newDatabaseURL
backupItemName:[[self.databaseURL lastPathComponent] stringByAppendingPathExtension:@"old"]
options:0
resultingItemURL:nil
error:&error];
if (!moved) {
NSLog(@"Unable to move temporary store: %@", error);
}
} else {
[[NSFileManager defaultManager] moveItemAtURL:self.databaseURL toURL:[self.databaseURL URLByAppendingPathExtension:@"old"] error:&error];
}

NSDictionary* options = nil;
if (self.automaticallyMigratePreviousCoreData) {
options = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES
};
}
NSPersistentStore *persistentStore = [_psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.databaseURL options:options error:&error];
if (!persistentStore) {
NSLog(@"Unable to add new store: %@", error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDestroyAllNSFetchedResultsControllers object:self];
if (error && self.defaultErrorBlock) {
self.defaultErrorBlock(self, error);
}
}

-(void)resetDatabase {
[self replaceDatabaseWithURL:nil];
}

@end