Do you have performance issues with your CoreData implementation because you’re blocking the main thread with some computations? This series can help you to solve your problems and improve the user experience of your App. Fourth Part: Delete.
Introduction
This is the fourth part of the series CoreData: CRUD With Concurrency In Swift
: DELETE.
If you didn’t read the first part, I would suggest you to read it since I introduced this series. You can find the second part here and the third one here.
In this article we are going to learn how to delete the data with CoreData using background queues—to avoid blocking the main queue.
CoreData provides mainly two ways to do it: Either using a NSManagedObject
or NSBatchDeleteRequest
.
Happy Reading!
Using NSManagedObject
Let’s consider that our App allows the user to delete a specific Dog
. In this scenario, we would have a NSManagedObject
object to delete.
Once we have a specific NSManagedObject
, we can delete it very easily. Let’s see in the examples below:
iOS 8+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| privateManagedObjectContext.perform {
let dog: Dog = // Dog to delete
do {
// Deletes dog
privateManagedObjectContext.delete(dog)
// Saves in private context
try privateManagedObjectContext.save()
mainManagedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try mainManagedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
} catch {
fatalError("Failure to save context: \(error)")
}
}
|
iOS 10+
1
2
3
4
5
6
7
8
9
10
11
12
13
| persistentContainer.performBackgroundTask { privateManagedObjectContext in
let dog: Dog = // Dog to delete
do {
// Deletes dog
privateManagedObjectContext.delete(dog)
// Saves in private context
try privateManagedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
|
When we delete a NSManagedObject
object, then we must save these changes manually—like in the examples above—to update our database.
Using NSBatchDeleteRequest
The approach of Using NSManagedObject
has a problem. If we want to delete all the dogs in our system with a specific name, for example all the Max
dogs, we should create a NSManagedObject
per dog to delete and then delete all of them manually.
Fortunately, Apple introduced, in iOS 9, a better way to delete all the entries which satisfy the criteria of a specific predicate: NSBatchDeleteRequest
.
It’s very similar to NSBatchUpdateRequest
, which we have seen in the previous part of this series.
For the sake of explanation, let’s consider that we want to delete all the Dog
s with the name Max
. Let’s see how to do it using NSBatchDeleteRequest
:
iOS 9+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| privateManagedObjectContext.perform {
// Creates a request for entity `Dog`
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")
// All the dogs with `name` equal to "Max"
let predicate = NSPredicate(format: "name == %@", "Max")
// Assigns the predicate to the request
request.predicate = predicate
// Creates new batch delete request with a specific request
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
// Asks to return the objectIDs deleted
deleteRequest.resultType = .resultTypeObjectIDs
do {
// Executes batch
let result = try privateManagedObjectContext.execute(deleteRequest) as? NSBatchDeleteResult
// Retrieves the IDs deleted
guard let objectIDs = result?.result as? [NSManagedObjectID] else { return }
// Iterates the object IDs
objectIDs.forEach { objectID in
// Retrieve a `Dog` object queue-safe
let dog = mainManagedObjectContext.object(with: objectID)
// Updates the main context
mainManagedObjectContext.refresh(dog, mergeChanges: false)
}
} catch {
fatalError("Failed to execute request: \(error)")
}
}
|
iOS 10+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| persistentContainer.performBackgroundTask { privateManagedObjectContext in
// Creates a request for entity `Dog`
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")
// All the dogs with `name` equal to "Max"
let predicate = NSPredicate(format: "name == %@", "Max")
// Assigns the predicate to the request
request.predicate = predicate
// Creates new batch delete request with a specific request
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
// Asks to return the objectIDs deleted
deleteRequest.resultType = .resultTypeObjectIDs
do {
// Executes batch
let result = try privateManagedObjectContext.execute(deleteRequest) as? NSBatchDeleteResult
// Retrieves the IDs deleted
guard let objectIDs = result?.result as? [NSManagedObjectID] else { return }
// Updates the main context
let changes = [NSUpdatedObjectsKey: objectIDs]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [mainManagedObjectContext])
} catch {
fatalError("Failed to execute request: \(error)")
}
}
|
As we saw in these examples, there are three main points to delete our data:
- Create a request to filter the entity to delete.
- Execute the request with
resultType
set to resultTypeObjectIDs
, since we need just the IDs of the objects deleted. - Update the main context.
Note
If we want to support iOS 8, then we should fetch and delete all the entries manually like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| privateManagedObjectContext.perform {
// Creates a request for entity `Dog`
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")
// All the dogs with `name` equal to "Max"
let predicate = NSPredicate(format: "name == %@", "Max")
// Assigns the predicate to the request
request.predicate = predicate
// fetch returns just `objectID`s since we don't need of all the properties
request.includesPropertyValues = false
// Gets fetch result
guard let dogs = try privateManagedObjectContext.fetch(request) as? [NSManagedObject] else { return }
// Deletes dogs manually
for dog in dogs {
privateManagedObjectContext.delete(dog)
}
do {
// Saves in private context
try privateManagedObjectContext.save()
mainManagedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try mainManagedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
} catch {
fatalError("Failure to save context: \(error)")
}
}
|
We used the fetch property includesPropertyValues
which lets return just the objectID
s of the fetched elements. To delete an object we don’t need all its property, its objectID
is enough.
Conclusion
We’ve just finished our journey in the CoreData concurrency world. Now, we are ready to ship faster Apps using CoreData in background queues. Concurrency is a scary world, fortunately CoreData is able to provide us an easy way to manage different queues without hurting ourself.