In our last blog post about Core Data features in iOS 8, we have learned about NSBatchUpdateRequest
. This time, we will dig into another new feature: asynchronous fetch requests.

Asynchronous fetch requests
The second feature Apple introduced in Core Data in iOS 8, is the possibility to execute fetch requests asynchronously and therefore to schedule a time consuming fetch in the background. For that, another subclass of NSPersistentStoreRequest
has been introduced: NSAsynchronousFetchRequest
.
Fetching objects can be time consuming for different reasons: numerous records, complicated predicates, sort descriptors and so on. If this long operation is executed in the foreground, the main thread, also responsible for the UI, will block and the application won’t be responsive anymore. With background fetch requests, the fetch can be dispatched to a dedicated queue in the background, keeping the main thread safe and your UI responsive.
At this point, one can argue that using GCD to dispatch a regular fetch request in the background is just as good. There are however two reasons why it is not :
- First and foremost, it is a pain to do with GCD. You have to instanciate a new
NSManagedObjectContext
object in the background thread, create aNSFetchRequest
to fetch the ids of the objects you want, pass the ids back to the main thread and finally retrieve theNSManagedObjects
one by one to use them. - The second reason is that asynchronous fetch requests allow, under certain conditions, to track the progress of the operation or to cancel it. More on that later.
In practice
Let’s see how to use NSAsynchronousFetchRequest
with an example. We will once again assume our database contains a substantial set of articles, that we will now want to query and order by title. This can be quite the heavy task depending of the number of items fetched.
First, the NSFetchRequest
is created as usual.
// The fetch request we would normally use
NSFetchRequest*fetchRequest=[NSFetchRequestfetchRequestWithEntityName:@"Article"];fetchRequest.sortDescriptors=@[[NSSortDescriptorsortDescriptorWithKey:@"title"ascending:YES]];
Then, instead of calling [NSManagedObjectContext executeFetchRequest:error:]
, the fetch request is wrapped in an NSAsynchronousFetchRequest
.
NSAsynchronousFetchRequest*asynchronousFetchRequest=[[NSAsynchronousFetchRequestalloc]initWithFetchRequest:fetchRequestcompletionBlock:^(NSAsynchronousFetchResult*result){if(result.operationError){/* Handle the error */}else{NSArray*articles=result.finalResult;dispatch_async(dispatch_get_main_queue(),^{/* update your UI on main thread */});}}];
Note that the fetched articles are now available in the completion block, but any update to the UI must be dispatched to the main queue.
Finally, the method [NSManagedObjectContext executeRequest:error:]
starts the asynchronous request and returns an NSPersistentStoreAsynchronousResult
.
NSPersistentStoreAsynchronousResult*result=(NSPersistentStoreAsynchronousResult*)[self.managedObjectContextexecuteRequest:asynchronousFetchRequesterror:NULL];
Note: to use asynchronous fetch request, be careful to change the way you initialize the NSManagedObjectContext
in the Core Data stack if you don’t want to run into the following exception:
NSConfinementConcurrencyTypecontext<NSManagedObjectContext:0x...>cannotsupportasynchronousfetchrequest<NSAsynchronousFetchRequest:0x...>withfetchrequest<NSFetchRequest:0x...>
You have to use the dedicated initializer [NSManagedObjectContext initWithConcurrencyType:]
with one of the following types:
NSConfinementConcurrencyType
is the default value. It does not allow asynchronous fetch requests and has been labeled obsolete by Apple. It should be avoided.NSPrivateQueueConcurrencyType
to schedule work on a private queueNSMainQueueConcurrencyType
to schedule work in the main queue
Track the progress
The advantage of NSAsynchronousFetchRequest
is the ability to track the progress of the background fetch operation.
The executeRequest
method used to start the asynchronous fetch request returns a NSPersistentStoreAsynchronousResult
right away. This result has a NSProgress * progress
property. This property has two (undocumented) purposes: the object itself can help track the current progress of the asynchronous request, or stop it completely using the cancel
method.
To track the progress, you first have to provide a value to the estimatedResultCount
property of your fetch request. Otherwise Core Data will not be aware of the number of expected rows and will not be able to estimate the progress. Then create a parent NSProgress
and register as an observer for the key @"fractionCompleted"
of the child’s progress (the one from the fetch result).
// Create the parent progress
NSProgress*parentProgress=[NSProgressprogressWithTotalUnitCount:1];[parentProgressbecomeCurrentWithPendingUnitCount:1];//
/* ... execute the asynchronous fetch request and get the result *///
// Register as observer for key @"fractionCompleted" for the fetch progress
[result.progressaddObserver:selfforKeyPath:@"fractionCompleted"options:NSKeyValueObservingOptionInitialcontext:NULL];
What happens under the hood is that each time a new record is fetched, Core Data divides the count of fetched objects by the estimatedResultCount
to update the fractionCompleted
property of the progress like so:
fractionCompleted=objectsFetchedCount/estimatedResultCount
Notice that if the estimatedResultCount
is too low, meaning that there are more objects to fetch than we previously thought, fractionCompleted
can be greater than 100%. That’s why Core Data updates internally the value of the estimate result count as sketched below:
while(!finished){/* ... fetch objects ... */if(fetchedObjectsCount>=estimatedResultCount){estimatedResultCount=2*estimatedResultCount;}fractionCompleted=fetchedObjectsCount/estimatedResultCount;/* ... pass the fractionCompleted to the progress ... */}
Beware that an incorrect estimatedResultCount
can spoil your progress view. If too low, it will reach 100%, then be set back to 50% (as the estimated value is doubled) and so forth. If too high, it may well stick at 10% for a few seconds, and instantly reach 100%, defeating its purpose of sending back meaningful informations to the user. If you are unable to provide an accurate estimate, you can simply display an UIActivityIndicator
which would still inform the user of a background task in progress.
Conclusion
As seen previously, Apple’s iOS8 provides a built-in way to perform time-consuming NSFetchRequests
in the background. Additionally, if you have a precise idea of the number of objects that will be returned from a fetch request, there is an opportunity to track and display its progress to the final user.
We do hope there will be more documentation available soon, and in the meantime you can send us feedback or comments on twitter.