7

I'm using Core Data with an in-memory store, and I want to wipe it completely at some point. Most of the questions that I've found that relate to this are in regards to an on-disk store and involve deleting the store file or all the managed objects.

Is there a simpler way when it's in-memory? Can I just set something to nil and be done with it?

Community
  • 1
  • 1
Doug Smith
  • 29,668
  • 57
  • 204
  • 388
  • 1
    release (set to `nil`) all references to your data stack (MOCs, Coordinator, Model ...) – Dan Shelly Apr 25 '14 at 03:34
  • @DanShelly Could you elaborate as to how I'd do that? Do I have to recreate anything after that? – Doug Smith Apr 25 '14 at 16:14
  • You probably keep a strong reference to your main CoreData stack components. wherever that may be, set these properties to `nil` thus forcing them eventually to get deallocated. after that was completed, recreate the stack. – Dan Shelly Apr 25 '14 at 22:43
  • @DanShelly I can't wait for them to be "eventually" deallocated before I recreate the stack though, I need it to be as instant as possible. – Doug Smith Apr 28 '14 at 04:25
  • 1
    "eventually" mean the next run loop (autorelease pool drain) in the worst case. you can recreate the stack immediately after you release the previous one without any issues (they would not share memory) – Dan Shelly Apr 28 '14 at 04:47

3 Answers3

11

In the case of an in-memory store remove the store from the NSPersistentStoreCoordinator using removePersistentStore:error:. At that point you can create a new in-memory store attached to the coordinator if you want, which will give you a blank slate at the store level - which seems to be the focus of your question.

Note that if you are retaining managed objects that you fetched from an NSManagedObjectContext those may still stick around, and when a fault is fired on them after you have removed your in-memory store they can throw exceptions. It's advisable that if this is likely (usually because you are using a single context, or are not good about using Instruments to track memory use) that you also call reset on your managed object context(s) and remove any lingering strong references to managed objects.

So using your code: When you create the in-memory store, specify a URL so you can identify it later:

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:[NSURL URLWithString:@"memory://0"] options:nil error:&error]) {

In your resetStack method you would remove it after you reset and removed your managed object context(s):

[[self persistentStoreCoordinator] removePersistentStore:[[self persistentStoreCoordinator] persistentStoreForURL:[NSURL URLWithString:@"memory://0"] ] error:&error];

If you do not reset and remove the context(s) first, you will start to see exceptions as the objects get faults fired that no longer have a store to go to.

You should listen for the notifications NSPersistentStoreCoordinatorStoresDidChangeNotification and NSPersistentStoreCoordinatorStoresWillChangeNotification anywhere you are using Core Data, so that when a store is added or removed you can act accordingly. For example, if you call your resetStack method that removes a store, you should see the NSPersistentStoreCoordinatorStoresWillChangeNotification and use that opportunity to stop whatever you are doing that may be accessing that store. When NSPersistentStoreCoordinatorStoresDidChangeNotification is received, you would complete your tear down of objects or processes that may be using Core Data - like an NSFetchedResultsController that may be attempting to access the store that was just removed. Once the old store has been removed you want to add a new, clean one. When that happens you will get the same notifications, with payloads that indicate a store was added - and at that time you can create new managed object contexts and fetched results controllers to replace the old ones.

This is all actually a lot simpler than it sounds. Nonetheless, make sure you're doing all of this for the right reasons.

quellish
  • 21,123
  • 4
  • 76
  • 83
  • Sorry I have updated my answer to be more explicit. When I said "remove the store from the coordinator" I assumed you would call ``removePersistentStore:error`` on the coordinator. – quellish Apr 29 '14 at 06:55
  • Hmm. I implemented all these suggestions seemingly, but it still won't work (see this project): http://cl.ly/3F1Z0W1i1E0g And it gives me this error: `Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Object's persistent store is not reachable from this NSManagedObjectContext's coordinator with userInfo` – Doug Smith Apr 30 '14 at 03:30
  • Your managed object context still exists, but the coordinator no longer has a store. – quellish Apr 30 '14 at 05:35
  • Change this line: ``NSError *persistentStoreError;`` to these two lines: ``NSError *persistentStoreError = nil; _managedObjectContext = nil;``. You will then get an exception when your remaining context attempts to save. You can track down the problem with your context or add a new in memory store after the removal of the old one is complete (listen for the notification of the removal). – quellish Apr 30 '14 at 05:41
  • Thanks quellish. I'm still having some trouble though, when I fill in the suggestions ([see here for a project](http://cl.ly/3N0U1T3c0z1e)) I get `Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 12 in section at index 0`. Is it possible to amend my project to show how it should be done? If not I'd greatly appreciate a simple explanation as to what I'm doing wrong. Thanks so much for all your help. – Doug Smith May 07 '14 at 00:36
  • Your fetched results controller is still attempting to access objects that are not there. You added a cache to it (which was not there previously). The cache stores section information and managed object ids. Remove the cache or you will continue to have this issue. – quellish May 07 '14 at 02:54
  • How do I remove the cache? And what cache are you talking about specifically? Where I define the `cacheName`? If I set that to nil, the issue persists. – Doug Smith May 07 '14 at 17:12
  • Correct, where you create the fetched results controller and set the cache name to a non-nil value. That tells the fetched results controller to use it's cache. I'm not sure what prompted you to do that. That is one part of the issue. The other is that your table view is still using the fetched results controller feed the data source, and the table view's state has not been changed since the store was removed. Have that view controller listen for the store change notification and `reloadData` when that happens. Otherwise it will not know that the data model that feeds the data source changed. – quellish May 07 '14 at 23:32
  • Okay, so I've set the `cacheName` to nil, and I listen for the notification of the persistent store changing, and when it happens I call `reloadData on the `tableView`. (Project: http://cl.ly/2g2I0T0F0n1I) Yet it crashes with `Post *post = [self.fetchedResultsController objectAtIndexPath:indexPath];` Did I not follow a step correctly? – Doug Smith May 15 '14 at 06:21
  • When the persistent store is removed, the fetched results controller's context has nothing backing it. You need to remove the fetched results controller when the store it's using is removed. – quellish May 15 '14 at 08:00
  • How would I best do that? If I set `self.fetchedResultsController` to `nil` in the Master View Controller (the one with the fetched results controller) the table view is still empty after the second set of data loads. – Doug Smith May 17 '14 at 06:47
  • When you say "the second set of data loads" I assume you are creating a new persistent store, coordinator, and managed object context after removing the previous ones. You would also have to create a new fetched results controller. – quellish May 17 '14 at 08:36
  • Have you seen my project? Is there any way you could show me specifically where I should be doing this? I'm feeling very confused. – Doug Smith May 17 '14 at 17:21
  • Yes. After you call your "resetStack" method, the fetched results controller that drives your table view is still trying to use the managed object context that is no longer valid. You should listen for the notification that the stores were changed, and when that happens you need to create a new store and new fetched results controller. – quellish May 23 '14 at 08:49
  • In your specific case, in your test project's `didChangePersistentStores` you SHOULD be creating a new fetched results controller. At the very least you need to refetch, though this is still dangerous. That is moving farther and farther from your original question though. – quellish May 23 '14 at 09:09
3

As I've noted in my comment:
It is sufficient to set the CoreData stack components to nil

Assuming a boilerplate stack you could achieve that by:

In you AppDelegate .m file, make the core components writable:

@interface DSAppDelegate ()
@property (readwrite, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readwrite, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readwrite, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@end

Add a reset function:

- (void) resetStack
{
    self.managedObjectContext = nil;
    self.persistentStoreCoordinator = nil;
    self.managedObjectModel = nil; //not necessary if store will not change
}

calling resetStack will release memory occupied by the in-memory store (might take some time if the store is heavily populated).

Remember not to use any stack component during the reset, or keeping strong references to them as you might not get the stack to reset properly

This is demonstrated in THIS test project.
(In instruments, view memory allocations relative to the console output).

Edit:
@quellish solution is more elegant (removing and adding a new store), and instead of replacing the managed context of the view controller you need only reset it, although you will have to nullify your FRC in any case.

Dan Shelly
  • 5,991
  • 2
  • 22
  • 26
  • I can't seem to replicate it. I download from an API, add it to Core Data, call reset stack, download more, but it never replaces the current data. Here's a project showing it: http://cl.ly/3d1L272a3G1e – Doug Smith Apr 29 '14 at 01:21
  • You are not releasing the MOC in your view (most likely) – Dan Shelly Apr 29 '14 at 03:42
  • I just tried setting it to `nil` before I call `resetStack` and it still doesn't work. – Doug Smith Apr 29 '14 at 03:52
  • Glancing at your code, I noticed you have not reset your [FRC](https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html) which hold the MOC given to it strongly. You must notify all users of the current stack to relinquish all stack related references as they are about to go invalid or keep the old stack alive. – Dan Shelly Apr 29 '14 at 04:08
0

In case of Core Data In-memory store, the data is persisted until your app is quit or suspended. So you can just leave it to the OS to suspend your app when there are any memory issues in the device.

Alternatively you can force the app to quit by calling exit(0), in the applicationDidEnterBackground:(UIApplication *)application method. But this is not recommended.

The safest method would be to manually iterate through your database, deleting all the content.

Adithya
  • 4,545
  • 3
  • 25
  • 28