Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

CoreData: CRUD With Concurrency in Swift – Part 4

DZone's Guide to

CoreData: CRUD With Concurrency in Swift – Part 4

In Part 3 of this series on concurrency in Swift, learn the two main methods of deleting data with CoreData, using background queues.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

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 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 two main 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+

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+

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, 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, in iOS 9, Apple introduced a better way of deleting 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+

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+

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:

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 us return just the objectIDs of the fetched elements. To delete an object, we don’t need all its properties; its objectID is enough.

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 ourselves.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
mobile app development ,swift ,coredata ,mobile

Published at DZone with permission of Marco Santarossa, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}