fabernovel loader

Oct 16, 2014 | 4 min read

Tech

Core Data Features in iOS 8 (Part 2)

Pierre Felgines

Developer Senior


FABERNOVEL TECHNOLOGIES
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 a NSFetchRequest to fetch the ids of the objects you want, pass the ids back to the main thread and finally retrieve the NSManagedObjects 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 = [NSFetchRequest fetchRequestWithEntityName:@"Article"];
fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"title"
                                                               ascending:YES]];

Then, instead of calling [NSManagedObjectContext executeFetchRequest:error:], the fetch request is wrapped in an NSAsynchronousFetchRequest.

NSAsynchronousFetchRequest * asynchronousFetchRequest =
  [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest
                                           completionBlock:^(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.managedObjectContext executeRequest:asynchronousFetchRequest error: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:

NSConfinementConcurrencyType context <NSManagedObjectContext: 0x...> cannot support asynchronous
fetch request <NSAsynchronousFetchRequest: 0x...> with fetch request <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 queue
  • NSMainQueueConcurrencyType 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 = [NSProgress progressWithTotalUnitCount:1];
[parentProgress becomeCurrentWithPendingUnitCount:1];
//
/* ... execute the asynchronous fetch request and get the result */
//
// Register as observer for key @"fractionCompleted" for the fetch progress
[result.progress addObserver:self
                  forKeyPath:@"fractionCompleted"
                     options:NSKeyValueObservingOptionInitial
                     context: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.

Contact @Applidium
logo business unit

FABERNOVEL TECHNOLOGIES

150 talents to face technological challenges of digital transformation

next read