16

Basically, what I'm trying to do is to wipe out all data in my CoreData persistent store, then import new data. How would you do this? It seems that the simplest solution is call [NSPersistentStoreCoordinator removePersistentStore:error:] and then remove the file. Is that the best practice available? Is it thread-safe?

Thank you very much,

#

Question 0.1: was

I am trying to update the data in a CoreData persistent store. My user is seeing a table view with statistical data. I want to update the application by deleting all existing data, then importing new data. I would like to show a progress view to tell the user that the application is not hanging.

I have added the following resetPersistentStore method in my AppDelegate (persistentStoreCoordinatoris given for reference):

// ...
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
// ...

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator != nil) {
        return persistentStoreCoordinator;
    }

    NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: kPersistentStoreFilename]];

    NSError *error = nil;
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    

    return persistentStoreCoordinator;
}

/**
 * Will remove the persistent store
 */
- (NSPersistentStoreCoordinator *)resetPersistentStore {
    NSError *error;

    [managedObjectContext lock];

    // FIXME: dirty. If there are many stores...
    NSPersistentStore *store = [[persistentStoreCoordinator persistentStores] objectAtIndex:0];

    if (![persistentStoreCoordinator removePersistentStore:store error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }  

    // Delete file
    if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    } 

    // Delete the reference to non-existing store
    [persistentStoreCoordinator release];
    persistentStoreCoordinator = nil;

    NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];
    [managedObjectContext unlock];

    return r;
}

Then in my view I do (in another thread since I am using MBProgressHUD:

PatrimoineAppDelegate *appDelegate = (PatrimoineAppDelegate *)[[UIApplication sharedApplication] delegate]; 
// Delete everything
[appDelegate resetPersistentStore];

And I get an EXC_BAD_ACESS...

I do not know CoreData or multithreading very well, maybe I'm doing an evident error...

David
  • 3,285
  • 1
  • 37
  • 54
charlax
  • 25,125
  • 19
  • 60
  • 71

7 Answers7

11

For those attempting this on iOS9+, there are now the destroyPersistentStoreAtURL and replacePersistentStoreAtURL APIs.

Phil Loden
  • 1,394
  • 1
  • 15
  • 15
  • 2
    Do you know of any information that explains how to use these methods? Apple's documentation is sparse. – SAHM Nov 30 '17 at 00:39
  • 2
    specifically not sure which options are applicable since the documentation does not say at all – SAHM Nov 30 '17 at 13:40
8

If your goal is to empty the data store and reload it with new information, you may be better off using NSManagedObjectContext's reset and then loading in new data.

From NSManagedObjectContext's documentation

A context always has a “parent” persistent store coordinator which provides the model and dispatches requests to the various persistent stores containing the data. Without a coordinator, a context is not fully functional. The context’s coordinator provides the managed object model and handles persistency. All objects fetched from an external store are registered in a context together with a global identifier (an instance of NSManagedObjectID) that’s used to uniquely identify each object to the external store.

Removing the persistent store and using the managed object context associated with the store is probably the cause of the error.

Giao
  • 14,725
  • 2
  • 24
  • 18
  • 3
    Just to be sure to understand: my data are already saved in the persistent store. A simple `[moc reset]` (followed by a save) will wipe everything in the sqlite file too? – charlax Feb 18 '10 at 18:08
  • This solved a problem that has been deviling me for _day_! Thanks! Problem: how to update data in the tableview that's been edited by an external process: Invoke [myManagedObjectContext reset]; immediately before calling for new data from the fetchedResultsController. I haz many happies! – mpemburn Jun 06 '12 at 10:40
  • charlax, he doesn't explicitly say that reset followed by a save will indeed reset. – user4951 Oct 07 '12 at 12:35
  • 1
    @Giao Is it fair enough to delete persistentstore or it is a bad practice? Please suggest me. – Exploring Sep 19 '13 at 15:08
  • 2
    For what it's worth, reseting the context does not change the content of the store at all. It simply erased whatever has been loaded into the context. – MiKL Aug 29 '14 at 09:01
5

Still not work! Abort caused by ManagedObjectContext link with invalid persistance store. Finally it work if I delete the ManagedObjectContext and let the app re-create later

Here is my modification

- (NSPersistentStoreCoordinator *)resetPersistentStore 
{
  NSError *error = nil;

  if ([persistentStoreCoordinator_ persistentStores] == nil)
    return [self persistentStoreCoordinator];

  [managedObjectContext_ release];
  managedObjectContext_ = nil;

  // FIXME: dirty. If there are many stores...
  NSPersistentStore *store = [[persistentStoreCoordinator_ persistentStores] lastObject];

  if (![persistentStoreCoordinator_ removePersistentStore:store error:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
  }  

  // Delete file
  if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
    if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
      NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
      abort();
    } 
  }

  // Delete the reference to non-existing store
  [persistentStoreCoordinator_ release];
  persistentStoreCoordinator_ = nil;

  NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];

  return r;
}
SuryC
  • 1
  • 1
  • 1
  • This did the trick in my case. Before, I was manually re-creating the persistent store, which threw an error: Object's persistent store is not reachable from this NSManagedObjectContext's coordinator. By assigning the persistent store and managed object context to nil, you force the app to re-create it on its own, which gets rid of this error. Thank you! – Michael D. Dec 29 '11 at 15:42
4

Here is the solution. There may be some more elegant options (lock...) but this one works.

/**
 * Will remove the persistent store
 */
- (NSPersistentStoreCoordinator *)resetPersistentStore {
    NSError *error = nil;

    if ([persistentStoreCoordinator persistentStores] == nil)
        return [self persistentStoreCoordinator];

    [managedObjectContext reset];
    [managedObjectContext lock];

    // FIXME: dirty. If there are many stores...
    NSPersistentStore *store = [[persistentStoreCoordinator persistentStores] lastObject];

    if (![persistentStoreCoordinator removePersistentStore:store error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }  

    // Delete file
    if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
        if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        } 
    }

    // Delete the reference to non-existing store
    [persistentStoreCoordinator release];
    persistentStoreCoordinator = nil;

    NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];
    [managedObjectContext unlock];

    return r;
}
charlax
  • 25,125
  • 19
  • 60
  • 71
  • 7
    That is terribly ugly and overcomplicated. Why are you not just rebuilding the entire Core Data stack and handing off the new context to your view controller? Swapping out the PSC under the `NSManagedObjectContext` can be very risky. Secondarily you should never hit a `NSManagedObjectContext` from multiple threads, the stack is designed so that you create one context per thread for a reason; the context handles locking *correctly*. You are better off either rebuilding the entire stack or actually deleting the objects from your store and then reloading it as opposed to deleting the file. – Marcus S. Zarra Feb 17 '10 at 15:37
  • 3
    Thank you Marcus, I'm not sure to hear you ; how would you do to wipe out all data in the DB, in broad terms? What does CoreData stack imply? Let's say there is already some data in the persistent store, I would like to delete everything and then import new data. If I understand Apple's doc, reseting the MOC does not change anything in the persistent store, am I right? – charlax Feb 18 '10 at 17:47
0

You can swap out (or delete) a persistent store and then reset the context (just make sure you refetch any objects you have in memory):

for (NSPersistentStore *store in persistentStoreCoordinator.persistentStores) {
    removed = [persistentStoreCoordinator removePersistentStore:store error:nil];
}

// You could delete the store here instead of replacing it if you want to start from scratch
[[NSFileManager defaultManager] replaceItemAtURL:storeURL
                                   withItemAtURL:newStoreURL
                                  backupItemName:nil
                                         options:0
                                resultingItemURL:nil
                                           error:nil];

NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];

[myContext reset];

I'm using this to perform an import into a temporary store on a private queue and then overwriting the main thread store and reloading everything when I'm finished. I was importing into a child context but deleting existing objects without side effects became troublesome in my case.

Justin Driscoll
  • 654
  • 4
  • 10
  • 3
    WARNING: a sqlite database is frequently more then one file, and only moving one will cause issues if you had any pending transactions (or other implementation defined state, but pending transactions is the biggest one). – Stripes Dec 18 '14 at 16:05
0

Before resetting the persistentStore you must first reset all the managedObjectContext associated with that or else all the managedObjects will not have context to be accessed this may cause error.

it is good to always remove the sqlite file directly from file system and set managedObjectContext and persistentStoreCoordinator to nil, rather than calling removePersistentStore. This will recreate the persistantStore and managedObjectContext next time you try to access or start storing.

Shub
  • 21
-4

I found the easiest way to deal with these types of problems, so long as you can reload your Core Data data, is to follow these steps:

(1) Reset the simulator.

(2) Delete the sqlite database from your project.

(3) Delete the simulator directory from your machine.

Surprisingly I found that doing steps 1 & 2 did not always work without step 3.

You will need to reload your Core Data store if you do this.

port5432
  • 5,889
  • 10
  • 60
  • 97
  • 1
    This does not address the original question which is how to reset the data in the persistent store at run-time in a production environment. – Jacob Jun 10 '13 at 16:43