Contents

CoreData: CRUD With Concurrency In Swift - Part 4


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 Dogs 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:

  1. Create a request to filter the entity to delete.
  2. Execute the request with resultType set to resultTypeObjectIDs, since we need just the IDs of the objects deleted.
  3. 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 objectIDs 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.