Introduction

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 to 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!

Contents

NSAsynchronousFetchRequest

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

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

Moreover, the constructor of NSAsynchronousFetchRequest has also 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 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 background to the main queue. To achieve it, 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 = [Dog]() // 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, interating the array just once. If we don’t use lazy , we would iterate the array twice since we are using two flatMap .

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

If we want to receive the 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 observer 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!

Share this: Facebook

LinkedIn

Twitter

Reddit

Pocket

More

Email

Print



