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

CoreData: CRUD With Concurrency in Swift – Part 3

DZone's Guide to

CoreData: CRUD With Concurrency in Swift – Part 3

In Part 2 of this series on concurrency in Swift, learn the two main methods of updating 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 third part of the series CoreData: CRUD With Concurrency In Swift: UPDATE.

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.

In this article, we are going to learn how to update 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 using NSBatchUpdateRequest.

Happy reading!

Contents

  • Using NSManagedObject
  • Using NSBatchUpdateRequest
  • Conclusion

Using NSManagedObject

Let’s consider that in our app we want to allow the user to add some dogs in a list called “Favorites.” We would need to add a new boolean attribute isFavorite in the Dog entity:

At this point, when a user wants to add the dog to the “Favorites” list, we have to set the property isFavorite to true in an object Dog.

Let’s see how to update a Dog and save the changes:

iOS 8+

let dogToUpdate = // `Dog` object to update

privateManagedObjectContext.perform {
    // Creates a queue-safe `dog` object as said in part two
    guard let queueSafeDog = privateManagedObjectContext.object(with: dogToUpdate.objectID) as? Dog else { return }
    // Sets dog as favorite
    queueSafeDog.isFavorite = true

    do {
        // Saves the entry updated
        try privateManagedObjectContext.save()

        // Performs a task in the main queue and wait until this tasks finishes
        mainManagedObjectContext.performAndWait {
            do {
                // Saves the data from the child to the main context to be stored properly
                try mainManagedObjectContext.save()
            } catch {
                fatalError("Failure to save context: \(error)")
            }
        }
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}

iOS 10+

let dogToUpdate = // `Dog` object to update

persistentContainer.performBackgroundTask { privateManagedObjectContext in
    // Creates a queue-safe `dog` object as said in part two
    guard let queueSafeDog = privateManagedObjectContext.object(with: dogToUpdate.objectID) as? Dog else { return }
    // Sets dog as favorite
    queueSafeDog.isFavorite = true

    do {
        // Saves the entry updated
        try privateManagedObjectContext.save()
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}

When we update a NSManagedObject object, we must save these changes manually—like in the examples below—to keep the persistence of these changes.

Using NSBatchUpdateRequest

As we said in "Using NSManagedObject," our app allows the user to add a dog to the list “Favorites.”

At this point, we want to add also the possibility to empty this list, removing all the dogs from this list. It means that we should update all the dogs in this list, setting isFavorite to false.

If we want to use the approach of "Using NSManagedObject," we should create a NSManagedObject per dog to update and then update all of them manually.

Fortunately, CoreData provides a better way to update all the entries which satisfy the criteria of a specific predicate: NSBatchUpdateRequest.

Let’s see how to use NSBatchUpdateRequest to empty the “Favorites” list:

iOS 8+

privateManagedObjectContext.perform {
    // Creates new batch update request for entity `Dog`
    let updateRequest = NSBatchUpdateRequest(entityName: "Dog")
    // All the dogs with `isFavorite` true
    let predicate = NSPredicate(format: "isFavorite == true")
    // Assigns the predicate to the batch update
    updateRequest.predicate = predicate

    // Dictionary with the property names to update as keys and the new values as values
    updateRequest.propertiesToUpdate = ["isFavorite": false]

    // Sets the result type as array of object IDs updated
    updateRequest.resultType = .updatedObjectIDsResultType

    do {
        // Executes batch
        let result = try privateManagedObjectContext.execute(updateRequest) as? NSBatchUpdateResult

        // Retrieves the IDs updated
        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 new batch update request for entity `Dog`
    let updateRequest = NSBatchUpdateRequest(entityName: "Dog")
    // All the dogs with `isFavorite` true
    let predicate = NSPredicate(format: "isFavorite == true")
    // Assigns the predicate to the batch update
    updateRequest.predicate = predicate

    // Dictionary with the property names to update as keys and the new values as values
    updateRequest.propertiesToUpdate = ["isFavorite": false]

    // Sets the result type as array of object IDs updated
    updateRequest.resultType = .updatedObjectIDsResultType

    do {
        // Executes batch
        let result = try privateManagedObjectContext.execute(updateRequest) as? NSBatchUpdateResult

        // Retrieves the IDs updated
        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 can see in these examples, there are four main points to update our data:

  1. Create the predicate to filter the entity to update.
  2. Set the new values with the property propertiesToUpdate. It’s a dictionary where we have the property names as keys and the new values as dictionary values. In our example, we added just an element, but this dictionary can have several elements, one per property to update.
  3. Execute the request with resultType set to updatedObjectIDsResultType.
  4. Update the main context.

Conclusion

We’ve just finished also our third adventure in the CoreData concurrency world. In the next—and last—article, we’ll see how to delete the data in a background queue. Stay tuned!

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 ,mobile app development ,swift ,ios ,data

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 }}