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

CoreData: CRUD With Concurrency in Swift - Part 2

DZone's Guide to

CoreData: CRUD With Concurrency in Swift - Part 2

In Part 2 of this series on concurrency in Swift, learn how to read data with CoreData, using background queues to avoid blocking the main queue.

· 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 second part of the series CoreData: CRUD With Concurrency In Swift: READ.

If you didn’t read the first part, I would suggest you read it since I introduced this series.

In this article, we are going to learn how to read some data with CoreData, using background queues—to avoid blocking the main queue.

Happy reading!

NSAsynchronousFetchRequest

To fetch the data asynchronously in a background queue, CoreData provides the object NSAsynchronousFetchRequest.

We can create this object by passing a NSFetchRequest argument in its constructor and then executing it thanks to the execute method of NSManagedObjectContext.

Moreover, the constructor of NSAsynchronousFetchRequest also has a closure parameter, which is called when the fetch finishes. This closure has also a fetch result object as parameter–NSAsynchronousFetchResult–to retrieve the data.

To perform this asynchronous fetch in a background queue, we must call the execute method using a private context. As we saw in the first part of this series, we have two ways to do it:

iOS 8+

let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.parent = mainManagedObjectContext

iOS 10+

let privateManagedObjectContext = persistentContainer.newBackgroundContext()

Once we have our private context, we are ready to perform our fetch:

// Creates a fetch request to get all the dogs saved
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")

// Creates `asynchronousFetchRequest` with the fetch request and the completion closure
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in

    // Retrieves an array of dogs from the fetch result `finalResult`
    guard let result = asynchronousFetchResult.finalResult as? [Dog] else { return }

    // Dispatches to use the data in the main queue
    DispatchQueue.main.async {
        // Do something
    }
}

do {
    // Executes `asynchronousFetchRequest`
    try privateManagedObjectContext.execute(asynchronousFetchRequest)
} catch let error {
    print("NSAsynchronousFetchRequest error: \(error)")
}

As we can see in the example above, we can retrieve the result of the fetch inside the completion closure using the property finalResult of the object passed as a parameter of this closure.

Note: Since we are using a NSFetchRequest to create a NSAsynchronousFetchRequest, we can also set to fetchRequest a NSPredicate to add a specific condition to our query and/or a NSSortDescriptor to order the query result.

Different Queues Communication

Since we’re fetching using a private context, the completion closure will be executed in a background queue. It means that if we want to use the data in the main queue—for tasks like updating the UI—we must pass the data from the background to the main queue. To achieve this, we can use DispatchQueue.main.async {}.

When we pass NSManagedObject between different queues, we should pay attention.

As we can read in the official documentation here: "NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances. You retrieve the managed object ID of a managed object by calling the objectID method on the NSManagedObject instance."

It means that if we want to use the data in the main queue, we should update our dispatch queue like this:

DispatchQueue.main.async {

    // Creates an array for the new dogs queue-safe
    var dogs = [UnitTest]()

    // Iterates the result of the private context
    for dog in result {
        // Retrieves the ID of the entity
        let objectID = dog.objectID

        // Creates a new dog entity queue-safe
        guard let queueSafeDog = mainManagedObjectContext.object(with: objectID) as? Dog else { continue }

        // Adds to the array
        dogs.append(queueSafeDog)
    }

    // Do something with new queue-safe array `dogs`
}

For the lovers of higher-order functions, we can write the example above in this way:

DispatchQueue.main.async {

    let dogs: [Dog] = result.lazy
        .flatMap { $0.objectID } // Retrives all the objectsID
        .flatMap { mainManagedObjectContext.object(with: $0) as? Dog } // Creates a new dog entity queue-safe for each objectID

    // Do something with new queue-safe array `dogs`
}

Remember to use the keyword lazy. In this way, the chain of higher-order functions will be combined in one function, iterating the array just once. If we don’t use lazy, we would iterate the array twice since we are using two flatMap.

More…

NSAsynchronousFetchRequest also allows us to receive notifications of the fetch progress. It’s a useful feature if we want to show the user some information about the fetching, like a progress HUD.

If we want to receive a notification of the progress, we can set a KVO observer, like in this example:

do {
    // Creates a new `Progress` object
    let progress = Progress(totalUnitCount: 1)

    // Sets the new progess as default one in the current thread
    progress.becomeCurrent(withPendingUnitCount: 1)

    // Keeps a reference of `NSPersistentStoreAsynchronousResult` returned by `execute`
    let fetchResult = try privateManagedObjectContext.execute(asynchronousFetchRequest) as? NSPersistentStoreAsynchronousResult

    // Resigns the current progress
    progress.resignCurrent()

    // Adds observer
    fetchResult?.progress?.addObserver(self, forKeyPath: #keyPath(Progress.completedUnitCount), options: .new, context: nil)

} catch let error {
    print("NSAsynchronousFetchRequest error: \(error)")
}

Then, we can use the KVO callback to read the new progress data:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == #keyPath(Progress.completedUnitCount),
        // Reads new value
        let newValue = change?[.newKey] {
        print(newValue)
    }
}

If you’re wondering why we have to create a new Progess object and set it as default one, the answer is that it’s the way to add a child progress—the one of NSPersistentStoreAsynchronousResult—in the current thread. You can find more details in the official documentation.

In this example, we observe completedUnitCount just for the sake of explanation; we can observe any Progess property.

Conclusion

We’ve just finished also our second adventure in the CoreData concurrency world. In the next article, we’ll see how to update 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 ,swift ,mobile app development

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