Case Studies

Data Magic: How to deal with asynchronous requests in iOS apps

22 Oct 2015
254
4 min

Users commonly choose smartphones taking into account the purposes why they need such a powerful device. Still whatever the purpose is, any smartphone anyway performs similar operations: store, process and output data. That is the goal of almost every mobile application. iOS provides its developers with a very powerful framework for data organization in an application Core Data. In fact, it is an object graph where we can add and delete data, as well as perform sorting using a sort descriptor (NSSortDescriptor) and predicate (NSPredicate). Core Data is a powerful tool that makes it easier to manipulate the necessary data at the right time. However, sometimes you can encounter a problem and solution comes only after the "deep search".

What problems can Core Data cause

While running, each Fetch Request blocks the main thread until it is executed. Consequently the screen ceases to perceive presses and does not respond to user actions. That's why the user has nothing to do but to await the 'heavy' app function execution. Such long-time running may be caused by various reasons: a large number of entries, the complex predicate, sort descriptor and so on. In some cases users' waiting is justified. But what if we need to allow the user to continue working with the application during request execution? This problem can be solved through multithreading (Grand Central Dispatch), running the request asynchronously.

However, even this does not allow us to manipulate the data before the end of the request, since the context is blocked. Moreover, by running the fetch, we won't be able to cancel the request. Imagine the situation: a user switched to a screen with a very long list, but changed his mind and went back to the previous screen. In this situation, we do not need to continue the execution of the request. It's quite a serious drawback, but there is no problem that could not be solved.

- (void)fetchAllEntitiesWithFetchRequest {
    [self.activityIndicator startAnimating];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Entity"];
        [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"number" ascending:YES]]];
        self.entities = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Done");
            [self.activityIndicator stopAnimating];
            [self.tableView reloadData];
        });
    });
}

What is the solution?

With the release of iOS 8 we received an opportunity to run long-time requests, which return results after the execution and do not block the user interface. The new class NSAsynchronousFetchRequest allows to get rid of this drawback elegantly. Using this request we can keep the code clean and transparent, as well as solve the problem of the main thread blocking. To use this class, we need to create a normal request of NSFetchRequest class and specify the unit, which will be executed after fetch results are received.

Unlike the solution with the use of multithreading, asynchronous request has several advantages. Firstly, it doesn't block the context. This allows us to perform other operations with the data during the request execution, such as add, read, modify, and delete data. Also, this class allows to track the progress of the request or cancel it any time. So above-mentioned problems are comprehensively solved. That's how easy it is to cope with such difficult at first glance tasks.

- (void)fetchAllEntitiesWithAsynchronousFetchRequest {
    void (^resultBlock)(NSAsynchronousFetchResult *) = ^(NSAsynchronousFetchResult *result) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.entities = result.finalResult;
            NSLog(@"Done");
            [self.activityIndicator stopAnimating];
            [self.tableView reloadData];
        });
    };
    [self.activityIndicator startAnimating];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Entity"];
    [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"number" ascending:YES]]];
    NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:resultBlock];
    self.asynchronousFetchResult = (NSAsynchronousFetchResult *)[self.managedObjectContext executeRequest:asynchronousFetchRequest error:nil];
}

Here is how the result of using NSAsynchronousFetchRequest class can be visually represented:

Core Data

Illustration of the result

While running the request, we can press Back button, which cancells the request execution, and interact with an app even if the request isn't yet executed. Done status shows the end of the request.

Core Data

In other case, the request has to be executed before an app lets us perform actions:

It should be realized that the asynchronous request is not a necessity, and it isn't worth to make all requests asynchronous in the current project. This is a very powerful tool that, if used properly, gives us productive results. In order to understand whether we need asynchronous request or not, we need to define some moments. First of all, whether the user has to interact with the application during a long-time running request? If not, it makes no sense to use this technology in app development. You just need to display the process of synchronous request performing.

If your application frequently uses fetch requests and these functions are time and resource consuming, it makes sense to use asynchronous requests. But if your application has very little data or requests are extremely rare, the use of such requests will not give us an increase in productivity of your application. The main thing is to decide whether you want to use a particular technology, or there are analogues that are more appropriate in a given situation.

Rate this article:
( ) ( ) ( ) ( ) ( )
(1362 ratings, average: 4.87 out of 5)
Back to top
As s part of our team, be ready for:
Competitive Base Salary
Comprehensive Benefits
Great Work Environment
Drug Free Workplace
Tell us more about yourself