A question I hear surprisingly often is how to remove every record of a Core Data entity. This is a valid question and it sounds trivial. Up until iOS 9 and OS X El Capitan, though, it wasn't that trivial at all. Because not every application requires iOS 9 or OS X El Capitan, I would like to start this tutorial by explaining how to solve the problem the old way.
Fetch, Delete, Repeat
How It Works
Before iOS 9 and OS X El Capitan, you had no other option but to fetch every record of the entity, mark it for deletion, and save the changes. The implementation of this solution is not difficult. In the example below, we create a fetch request to fetch every record of the Item
entity and delete the records one by one.
// Initialize Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Item")
// Configure Fetch Request
fetchRequest.includesPropertyValues = false
do {
let items = try managedObjectContext.executeFetchRequest(fetchRequest) as! [NSManagedObject]
for item in items {
managedObjectContext.deleteObject(item)
}
// Save Changes
try managedObjectContext.save()
} catch {
// Error Handling
// ...
}
To reduce the memory footprint of this implementation, we set includesPropertyValues
to false
. This option tells Core Data that no property data should be fetched from the persistent store. Only the object identifier is returned.
Drawbacks
This solution comes with a warning, though. If you are dealing with hundreds or thousands of records, deleting every record of an entity takes up a non-trivial amount of time and memory. Because every record needs to be loaded into memory to mark it for deletion, it is important to delete records in batches if you plan to delete a large number of records.
Core Data is a fantastic framework and I enjoy working with it. Working with complex object graphs becomes much easier. But Core Data can only do its magic because every record you work with is managed by a managed object context. For every operation you perform on a record, Core Data loads the record into memory. That is exactly why batch operations like this can be problematic, including a trivial task such as deleting records.
Batch Delete Request
How It Works
This brings us to the NSBatchDeleteRequest
class, introduced in iOS 9 and OS X El Capitan. The class enables developers to batch delete records. A batch delete request directly acts on the persistent store through the persistent store coordinator, bypassing the managed object context. Instead of loading every record into memory, a batch delete request directly affects one or more persistent stores, a SQLite database, for example.
In the next example, we delete every record of the Item entity using the NSBatchDeleteRequest
class.
// Create Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Item")
// Create Batch Delete Request
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try managedObjectContext.executeRequest(batchDeleteRequest)
} catch {
// Error Handling
}
A batch delete request uses a fetch request, an instance of the NSFetchRequest
class, to determine which records need to be deleted. You can configure the fetch request with predicates and sort descriptors to selectively delete the records you need removed from the persistent store.
Even though a batch delete request bypasses the managed object context, it is handed to the managed object context to be executed. This may seem odd, but remember that a managed object context keeps a reference to the persistent store coordinator it is associated with.
Don't confuse executeRequest(_:)
with executeFetchRequest(_:)
. The executeRequest(_:)
method accepts an instance of the NSPersistentStoreRequest
class, the parent class of NSFetchRequest
and NSBatchDeleteRequest
. Even though we don't know what the implementation of executeRequest(_:)
looks like, it most likely forwards the persistent store request to the persistent store coordinator. In fact, the NSPersistentStoreCoordinator
class defines a method with a similar name, executeRequest(_:withContext:)
. This method accepts a persistent store request and an NSManagedObjectContext
instance.
Drawbacks
This solution also comes with a warning. Because the batch delete request acts on one or more persistent stores, the managed object context(s) of your application are unaware of the results of the batch delete request. For the same reason, Core Data does not broadcast notifications about the deletion of the records and not all validation rules are in effect.
Deleting a Persistent Store
If your application has the need to start with a clean slate, then I have good news for you. OS X El Capitan and iOS 9 introduced the ability to safely destroy a persistent store. Before OS X El Capitan and iOS 9, developers had to jump through several hoops to destroy a persistent store, an operation not without risk.
The addition of destroyPersistentStoreAtURL(_:withType:options:)
resolves this problem. If you need to safely destroy a persistent store, then this is the only option available. It is key that the options you pass to destroyPersistentStoreAtURL(_:withType:options:)
are identical to the ones you pass to addPersistentStoreWithType(_:configuration:URL:)
to add the persistent store to the persistent store coordinator. You will run into problems if you don't.
do {
try persistentStoreCoordinator.destroyPersistentStoreAtURL(persistentStoreURL, withType: NSSQLiteStoreType, options: nil)
} catch {
// Error Handling
}
What's Next?
It is great to see that Apple continues to invest in the Core Data framework, improving it year over year. Deleting records in batches has been on the wish list of many developers and Apple finally added this option to Core Data starting from OS X El Capitan and iOS 9.