Core Data Asynchronous Fetching in iOS 8 and Swift
In this tutorial, I will explore Apple’s Core Data framework. In particular, I’m going to show how to perform asynchronous fetches, a feature that developers have desired for a long time, but has finally become available in iOS 8 (and OS X 10.10 Yosemite).
An asynchronous fetch allows developers to execute a fetch request without blocking the Managed Object Context for the duration of the fetch. As an extra feature, the asynchronous fetch is cancelable by the user and provides progress reporting through NSProgress.
When you perform a standard fetch request using the executeFetchRequest:error: method, the call does not return immediately. Instead, the thread is blocked until the fetch is completed (i.e. the fetched objects populate the Managed Object Context). An asynchronous fetch works differently. Developers perform an asynchronous fetch using the executeRequest:error: method (the same method used for batch updates). This method immediately returns anNSAsynchronousFetchResult. Once the fetch is completed, a completion block is executed.
Asynchronous fetches are only supported by Managed Object Contexts using either theNSPrivateQueueConcurrencyType or the NSMainQueueConcurrencyType(ConfinementConcurrencyType is not supported). Additionally, in order for the Managed Object Context to be updated at the end of the asynchronous fetch, you must specify it to do so.
Here’s how it works. First, create an NSFetchRequest as you would do in a standard fetch. You can use sort descriptors and predicates to be more selective on your data. Then, create anNSAsynchronousFetchRequest using the standard fetch request. After that, pass the asynchronous fetch request to the Managed Object Context executing theexecuteRequest:error: method on that context. The context immediately returns anNSAsynchronousFetchResult. At the same time, the context sends the fetch to the store. Now, you can continue editing that Managed Object Context performing fetches, faults, etc.
NSAsynchronousFetchRequest is a new subclass of NSPersistentStoreRequest. You can create an NSAsynchronousFetchRequest instance initializing it with an instance of aNSFetchRequest and a completion block.
NSAsynchronousFetchResult is a new subclass of NSPersistentStoreResult. It provides results or errors after the completion. It’s returned immediately by the Managed Object Context when you call executeRequest:error.
Fetching, Fetching, Fetching
Ok, now let’s see how to implement an asynchronous fetch request. Begin by creating a new project using the Single-View Application template. Name it AsynchronousFetch. Choose Swift as programming language and, of course, select Core Data.
For brevity, I am going to simplify some of the extra work you should do in a real app. First, I create fake data as I did in the post on the Batch Updates. So, select the Asynchronous_Fetch.xcdatamodeld file and add a new entity Person with two attributes:
- name of type string
- age of type int16
Generate the class file for the Person entity. To generate fake data let’s add this chunk of code inside the application:didFinishLaunchingWithOptions: of the AppDelegate class and run it just once.
for _ in (0..<900000) {
var person = NSEntityDescription.insertNewObjectForEntityForName("Person",
inManagedObjectContext: self.managedObjectContext!) as Person person.name = "Mary"
person.age = Float(arc4random() % 100)
}
var error : NSError? = nil
if !self.managedObjectContext!.save(&error) {
NSLog("Unresolved error \(error), \(error!.userInfo)")
abort()
}
After executing the app once, the persistent store is populated with 900 thousands records. Now, comment the above lines of code to avoid having to execute them again.
In the same method of the AppDelegate class, add the following lines of code:
let vc = self.window!.rootViewController as ViewController
vc.moc = self.managedObjectContext
This will pass the managed object context instance to the view controller.
For this example this is not relevant, but you should modify the line of code where you create the Managed Object Context. Search the managedObjectContext method in the AppDelegate class and modify the line:
var managedObjectContext = NSManagedObjectContext()
with the following line:
var managedObjectContext =
NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
Now, let’s move into the ViewController class. First, we need a property to hold the Managed Object Context created in the AppDelegate. So, add this property:
var moc: NSManagedObjectContext?
Next, let’s override the viewDidAppear: method, by adding the following method to the ViewController class:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
After calling it on super, let’s create a fetch request for the entity Person:
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person",
inManagedObjectContext: self.moc!)
fetchRequest.entity = entity
After this, we can create an asynchronous fetch request:
let asyncFetch = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) {
(result:NSAsynchronousFetchResult!) -> Void in
if result.finalResult.count > 0 {
// do here what you want with the data
}
NSLog("Done.")
}
As you can see, we are using the previously created fetch request to create the asynchronous fetch request object. The closure will be executed when the asynchronous fetch completes.
Since we want to report the progress of the fetch to the UI, let’s add a UIProgressView to the view of the view controller. Open the storyboard and add a progress view from the object library. Don’t forget to add autolayout. Create an outlet for the progress view:
@IBOutlet weak var progressView: UIProgressView!
Connect the progress view and the outlet. The progress view will show the progress of the fetch. To do so, we need to create an instance of NSProgress and make it observable.
Before creating the asynchFetch constant (see above), add the following lines of code:
var progress = NSProgress(totalUnitCount: 1)
progress.addObserver(self,
forKeyPath: "fractionCompleted",
options: NSKeyValueObservingOptions.Initial,
context: ProgressObserverContext)
Here, the first line creates an instance of NSProgress. The total unit count is 1, because the fetch is a stream operation and, since you don’t know how many objects you’re going to retrieve, you need to set its value to 1. The second line makes the view controller the observer of the progress instance. The fractionCompleted property is the observed property. TheProgressObserverContext represents the context used by the observer. You can define it before the ViewController class definition:
let ProgressObserverContext = UnsafeMutablePointer<Void>()
After the asyncFetch constant, add the following lines of code:
progress.becomeCurrentWithPendingUnitCount(1)
self.moc!.performBlock { // 1
var error : NSError?
var results = self.moc!.executeRequest(asyncFetch,
error: &error) as NSAsynchronousFetchResult
}
progress.resignCurrent()
Notice that line 1 (the performBlock: method) is not needed here, because we are using a single managed context. Here, I am adding this line, just to make sure that, if you are using two or more managed contexts in your app, you don’t forget to use it.
Finally, to make the fractionCompleted property KVO compliant, we need to implement theobserveValueForKeyPath:ofObject:change:context: method:
override func observeValueForKeyPath(keyPath: String!,
ofObject object: AnyObject!,
change: [NSObject : AnyObject]!,
context: UnsafeMutablePointer<Void>) {
if context == ProgressObserverContext {
NSOperationQueue.mainQueue().addOperationWithBlock() {
var progress = object as NSProgress
self.progressView.progress = Float(progress.fractionCompleted)
NSLog("%f", self.progressView.progress)
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object,
change: change,
context: context)
}
}
Now, it’s ready to run.
Conclusion
Asynchronous fetching is a very powerful feature. Primarily because, it allows developers to continue to use the managed object context while the asynchronous fetch is executing. Together with the new batch updates functionality, asynchronous fetching adds interesting ways to work with managed object contexts and persistent stores.