Upcoming and OnDemand Webinars View full list

New in Core Data and iOS 8: Asynchronous Fetching

Robert Edwards

In our last installment we covered the NSBatchUpdateRequest in iOS 8. Today we will outline the NSAsynchronousFetchRequest, along with its appropriate use cases.

Asynchronous Fetching

Using Core Data in a multi-threaded environment has always required a deep understanding of the framework and a healthy dose of patience. Asynchronous fetching is not necessarily a feature of a multi-threaded Core Data environment. However, its ability to alleviate a common performance issue could potentially free you up from the added complexity of a multi-threaded CoreData stack.

First, ask yourself the following questions:

  • Do my most time-consuming Core Data functions involve the actual execution of the fetch request?
  • Do I only need to read my objects asynchronously, leaving write operations to occur synchronously?
  • Can I allow meaningful interaction with my app to the user during fetching?

If you have answered “Yes” to these questions, an NSAsynchronousFetchRequest may be all you need to remedy your performance woes. As an example, an application with mostly pre-canned data that requires computationally expensive activities, such as sorting, filtering or charting, would be a good candidate for this approach.

Multi-threaded Core Data vs. Asynchronous Fetching

Setting up a separate context and thread for Core Data allows you to perform any necessary operations (Fetching, Inserting, Deleting, Modifying) on your managed objects on a background thread. This will free up your main thread for smooth user interaction. However, even on that background thread, each operation will block the context while it’s being performed.

An NSAsynchronousFetchRequest allows you to send off your fetch request to the NSManagedObjectContext and be notified via a callback block when the objects have been populated in the context. This means that, whether on the main thread or a background thread, you can continue to work with your NSManagedObjectContext while this fetch is executing.

Progress and Cancellation

NSAsynchronousFetchRequest uses NSProgress to allow you to get incremental updates on how many objects have been fetched via Key-Value Observing (KVO). The only catch with incremental notifications is that because database operations occur in streams, there is no upfront way for the NSAsynchronousFetchRequest to know exactly how many objects will be returned. If you happen to know this number in advance, you can supply this figure to the NSProgress instance for incremental updates.

An added benefit of using NSProgress is being able to give the user the ability to cancel a long-running fetch request.

Example

Continuing our unread items example from earlier, lets see how an asynchronous fetch with progress updates would look. Assuming we have pre-computed the number of unread items, our fetch request would look like this:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"MyObject"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"read == %@", @(NO)];

NSPersistentStoreAsynchronousFetchResultCompletionBlock resultBlock = ^(NSAsynchronousFetchResult *result) {
   NSLog(@"Number of Unread Items: %ld", (long)result.finalResult.count);   

   [result.progress removeObserver:self
                        forKeyPath:@"completedUnitCount"
                           context:ProgressObserverContext];

   [result.progress removeObserver:self
                        forKeyPath:@"totalUnitCount"
                           context:ProgressObserverContext];
};

NSAsynchronousFetchRequest *asyncFetch = [[NSAsynchronousFetchRequest alloc]
                                          initWithFetchRequest:fetchRequest
                                          completionBlock:resultBlock];

[context performBlock:^{
    //Assumption here is that we know the total in advance and supply it to the NSProgress instance
    NSProgress *progress = [NSProgress progressWithTotalUnitCount:preComputedCount];
    [progress becomeCurrentWithPendingUnitCount:1];

    NSAsynchronousFetchResult *result = (NSAsynchronousFetchResult *)[context
                                                               executeRequest:asyncFetch
                                                                        error:nil];

    [result.progress addObserver:self
                 forKeyPath:@"completedUnitCount"
                    options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
                    context:ProgressObserverContext];

    [result.progress addObserver:self
                 forKeyPath:@"totalUnitCount"
                    options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
                    context:ProgressObserverContext];

    [progress resignCurrent];
}];

Here we’ve started with our standard fetch request just as before. We then create an NSPersistentStoreAsynchronousFetchResultCompletionBlock that will be executed once the asynchronous fetch completes. This block will log the final count of returned objects to the console, as well as remove ourselves as an observer of the incremental progress updates. We assemble these two pieces together to create our NSAsynchronousFetchRequest.

Now, it is time to begin executing our fetch request. We first call the NSManagedObjectContext method performBlock to ensure our operation is dispatched on the correct queue. Before kicking off the request, we create an NSProgress instance with our pre-computed unread item count as the totalUnitCount. Immediately after we execute the asynchronous request, we are returned an NSAsynchronousFetchResult “future” instance. This object can be used to grab the fetch result’s NSProgress instance for observing incremental updates. Lastly, we make sure to call resignCurrent on our NSProgress instance so that the NSAsynchronousFetchResult’s NSProgress instance can take over.

The only thing left to do is to observe the KVO notifications being broadcast by the NSProgress instance for progress updates.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
        change:(NSDictionary *)change context:(void *)context {
    if (context == ProgressObserverContext) {
        if ([keyPath isEqualToString:@"completedUnitCount"]) {
            NSNumber *newValue = change[@"new"];
            NSLog(@"Fetched %@ objects", newValue);
        } else if ([keyPath isEqualToString:@"totalUnitCount"]) {
            NSNumber *newValue = change[@"new"];
            NSLog(@"Finished with %@ objects fetched", newValue);
        }
    }
}

Asynchronous Log Output

Continued Improvements

Asynchronous fetching enables a lightweight means to executing a long-running fetch without grinding the system to a halt, or getting tangled in a complex multi-threaded universe.

While I don’t think NSAsynchronousFetchRequest or NSBatchUpdateRequest, on their own, will be winning over Core Data detractors, it does show Apple’s continued improvement of the framework. Each feature attacks a specific pain point that Core Data enthusiasts have encountered in the past.

Be sure to check out the accompanying sample project detailing both of these features over on GitHub. And if you want to learn iOS 8 with us, check out our one-day iOS 8 bootcamps in cities across the U.S.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project