3

So I have a bunch of objects in Core Data and want them to auto delete after X amount of days (this would be based off of an NSDate). I did some searching and it seems that you can only delete one core data object at a time, not a group of them, let alone ones that are based off of a certain date. I'm thinking maybe to have a loop running going through each object - but that seems like it would be very processor heavy. Any ideas on where I should be looking to do this? Thanks.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
mlevi
  • 1,433
  • 3
  • 18
  • 30
  • possible duplicate of [CoreData delete multiple objects](http://stackoverflow.com/questions/14560900/coredata-delete-multiple-objects) – tkanzakic Jan 07 '15 at 21:06
  • @tkanzakic this isn't a duplicate, he knows how to delete multiple objects in a loop. He's specifically asking what to do when doing so would be unacceptably slow and leave the app totally unresponsive. – Abhi Beckert Jan 07 '15 at 21:09
  • Have you looked at the new batch updating feature of Core Data in iOS 8? It lets you do such performance intensive operations a lot faster. – Abizern Jan 07 '15 at 21:15

3 Answers3

3

A loop deleting objects one by one is the correct approach.

Deleting objects in Core Data is extremely processor heavy. If that's a problem, then Core Data is not suitable for your project, and you should use something else. I recommend FCModel, as a light weight alternative that is very efficient.

If you are going to stick with Core Data, it's a good idea to perform large operations on a background NSOperationQueue, so the main application is not locked up while deleting the objects. You need to be very careful with Core Data across multiple threads, the approach is to have a separate managed object context for each thread, both using the same persistent store coordinator. Do not ever share a managed object across threads, but you can share the objectID, to fetch a second copy of the same database record on the other managed object context.

Basically your background thread creates a new context, deletes all the objects in a loop, then (on the main thread preferably, see documentation) save the background thread context. This will merge your changes unless there is a conflict (both contexts modify the same object) — in that scenario you have a few options, I'd just abort the entire delete operation and start again.

Apple has good documentation available for all the issues and sample code available here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdConcurrency.html

It's a bit daunting, you need to do some serious homework, but the actual code is very simple once you've got your head around how everything works. Or just use FCModel, which is designed for fast batch operations.

Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
  • 2
    FCModel is an alternative in the sense of it being an SQL database. Core Data isn't a database; it's an object relationship graph, that has an option to persist it's data in an SQLite database. – Abizern Jan 07 '15 at 21:19
  • @Abizern a database is "A collection of data arranged for ease and speed of search and retrieval." Core Data is, therefore, a database. Also on iOS SQLite is the only data persistence option, the other two options available on OS X are not allowed on iOS. Binary Data Store and XML use too much RAM, potentially thrashes the disk causing premature failure of the flash memory. – Abhi Beckert Jan 07 '15 at 21:20
  • I would go with FCModel but I already have core data set up and a user base of people using the app - as far as I know you can't migrate data from Core Data to FCModel. Someone mentioned core data batch updates in iOS 8, would that be any good? (I'm a little new to all of this) – mlevi Jan 07 '15 at 21:21
  • @Yismo you can migrate from Core Data to FCModel, but it's not easy. I agree, stick with Core Data. Test how slow the delete is, and if it's slow then use a background queue as I suggested. – Abhi Beckert Jan 07 '15 at 21:23
  • 1
    @AbhiBeckert What I'm trying to say is that although it stores data in a database (an implementation detail), it isn't a database. You don't access it like a database (or at least you shouldn't) – Abizern Jan 07 '15 at 21:24
  • @Abizern I understand your point, I just think you're wrong. :-) I don't like such a strict definition of the word "database", I prefer the more general and widely accepted definition (it's the only one in a dictionary) which does classify Core Data as a database. – Abhi Beckert Jan 07 '15 at 22:02
0

It's not as processor heavy as you may think :) (of course it depends of data amount)

Feel free to use loop

- (void)deleteAllObjects
{
NSArray *allEntities = self.managedObjectModel.entities;
for (NSEntityDescription *entityDescription in allEntities)
{
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:entityDescription];

    fetchRequest.includesPropertyValues = NO;
    fetchRequest.includesSubentities = NO;

    NSError *error;
    NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

    for (NSManagedObject *managedObject in items) {
        [self.managedObjectContext deleteObject:managedObject];
    }

    if (![self.managedObjectContext save:&error]) {
        NSLog(@"Error occurred");
    }
}  
}
sztembi
  • 223
  • 2
  • 10
  • It is processor heavy. An app I've written can take as long as 5 minutes to perform the `for { delete }` loop. The same operation in FCModel takes ~0.5 seconds. – Abhi Beckert Jan 07 '15 at 21:10
  • Yes, FCModel is of course better apporach but some just want to use Core Data – sztembi Jan 07 '15 at 21:14
  • Do you know how I would do the date checking - to see if its older than a certain date? – mlevi Jan 07 '15 at 21:29
  • Hmm, IMO you have to add for example `creationDate` attribute to your entity and use NSPredicate for creationDate older than... – sztembi Jan 07 '15 at 21:34
  • so I have that, I'm just a bit confused on the NSPredicate part – mlevi Jan 07 '15 at 21:40
  • After `[fetchRequest setEntity:entityDescription];` add line: `[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"creationDate < %@", [NSDate dateWithTimeIntervalSinceNow:(- MINUTESFROMNOW )]]];` – sztembi Jan 07 '15 at 21:44
0

As others have noted, iterating over the objects is the only way to actually delete the objects in Core Data. This is one of those use cases where Core Data's approach kind of falls down, because it's just not optimized for that kind of use.

But there are ways to deal with it to avoid unwanted delays in your app, so that the user doesn't have to wait while your code chugs over a ton of delete requests.

If you have a lot of objects that need to be deleted and you don't want to have to wait until the process is complete, you can fake the initial delete at first and then later do the actual delete when it's convenient. Something like:

  1. Add a custom boolean attribute to the entity type called something like toBeDeleted with a default value of NO.
  2. When you have a bunch of objects to delete, set toBeDeleted to YES on all of them in a single step by using NSBatchUpdateRequest (new in iOS 8). This class is mostly undocumented, so look at the header file or at the BNR blog post about it. You'll specify the property name and the new attribute value, and Core Data will do a mass, quick update.
  3. Make sure your fetch requests all check that toBeDeleted is NO. Now objects marked for deletion will be excluded when fetching even though they still exist.
  4. At some point-- later on, but soon-- run some code in the background that fetches and deletes objects that have toBeDeleted set to YES.
Tom Harrington
  • 69,312
  • 10
  • 146
  • 170