Multitasking in iOS 7 - Bleeding Edge Press Developing an iOS 7 Edge (2013)

Bleeding Edge Press Developing an iOS 7 Edge (2013)

4. Multitasking in iOS 7

IN THIS CHAPTER

· Examples of the Background Fetch and Background Transfer Service APIs

· A discussion of fetch intervals and app usage pattern observations

· Tips for getting started with the Remote Notification API

So far we've discussed migrating the look and feel of your app from iOS 6 to iOS 7 and shown how to use the new Dynamic Text and Text to Speech APIs to design for accessibility. In this chapter we introduce the new multitasking APIs and illustrate how we can build an app that refreshes its content before the user opens it.

Keeping users engaged with an app is critical to the success of many businesses. Its often cheaper to up-sell or cross-sell to the existing user base than acquire new users. Without an engaging app, the acquisition costs will be lost as will the opportunity to up-sell or cross-sell that user on other paid products. One of the best strategies for ensuring a loyal user base is to feature fresh, relevant content and a snappy, easy-to-use interface.

Apple's iOS has long supported multitasking for specific background tasks, such as playing music and sending email, which were exclusive to applications developed by Apple and partners. The introduction of iOS 6 brought background tasks that allowed third-party developers to keep an application alive long enough to finish a database transaction, upload a file, or encode a short video. However, these tasks prevented the device from sleeping, adding unnecessary strain to the user's battery, and were difficult to recover from errors in the case of low battery or intermittent network connection. Fortunately, iOS 7 has introduced several new multitasking APIs that makes building such apps easier than ever before.

In iOS 7, Apple introduced new APIs that allow developers to perform more tasks in the background with less strain on the user's battery, including Background Fetch, Remote Notifications, and Background Transfer Service.

We'll explore these new APIs in the context of building an application that displays a random image of an animal to the user. Think of it like "Hot or Not" but with animals instead of people and no voting. Before we can delve into the new APIs, however, we first need to understand the changes to how iOS handles background tasks that make them possible.

Background Task Changes

In iOS 6, background tasks keep the device awake until the task has completed, even if the device is locked. Over the course of a day, the number of applications and tasks doing this causes severe strain on the user's battery.

The background task model has been changed to improve battery life in iOS 7. Unlike in previous versions of iOS, apps will not keep a device awake; the device goes to sleep shortly after a user locks their phone. Instead of a single app or background task keeping the phone awake, iOS now coalesces requests for background processing from multiple apps into shared, opportunistic time slices. These times are opportunistic in the sense that the requesting apps are allowed to process when the phone is already awake to check mail, receive a text message or push notification.

Its important to note that the amount of time available for an app to execute a background task isn't necessarily reduced, its just not guaranteed to be contiguous. This increases the complexity of developing background tasks that can handle errors, device restarts, and other unexpected conditions. For this reason, Apple provided the Background Transfer API to simplify the most common long-running background task: enqueuing large uploads and downloads.

In addition to the changes to the background task model, Apple also expanded user's ability to control which applications are allowed to run in the background. The user can swipe up to remove an app from App Switcher as in iOS 6; however, in iOS 7 this also stops the application from running in the background. Applications won't be launched in the background if the user has removed the app from App Switcher. There's also a new "Background App Refresh" settings dialog under "Settings > General Settings" which gives the user explicit control over which applications are granted the ability to be launched and resumed in the background.

Background Fetch

With Background Fetch, an application can refresh its content before the user opens it. In iOS 6 and earlier, the typical experience of a user launching an application is to immediately wait several seconds for the app to refresh its content. Background fetch enables the iOS 7 experience of allowing the user to instead start using the app immediately.

An application using the Background Fetch API will be launched in the background periodically based upon the user's actual usage patterns for the app. The goal of the iOS scheduler is for an app to complete a background fetch immediately prior to the user launching it to give the app the opportunity to have the freshest content possible. For example, if a user typically uses app XYZ at noon and app ABC at 6pm, then iOS will instruct XYZ to perform a background fetch just before noon and app ABC just before 6pm.

Apple gives developers a small bit of control over timing when the background fetch will be initiated. This control comes in the form of background fetch intervals. Before we delve into the details of implementing background fetch in an app, we must first understand the various background fetch intervals available in iOS 7.

Background Fetch Intervals

There are three background fetch intervals available:

1. UIApplicationBackgroundFetchIntervalNever

2. UIApplicationBackgroundFetchIntervalMinimum

3. Custom background fetch interval specified in seconds

The default interval is UIApplicationBackgroundFetchIntervalNever, which instructs iOS to never launch the app to perform a background fetch. In the majority case where the content is tailored for the user (requiring a user login), this default makes the most sense. Once the user has authenticated, the app should update the fetch interval to one of the other two options. Conversely, when the user logs out of the app, the fetch interval should be restored such that it doesn't perform anymore background fetches.

The second option, UIApplicationBackgroundFetchIntervalMinimum, allows iOS to make an intelligent decision about when to launch or resume the app for performing a background fetch. This is the primary mechanism by which iOS coalesces background task requirements and applies its knowledge about the user's application usage patterns. It is the recommended fetch interval in most cases.

The last option allows application developers to specify a minimum fixed time during which a background fetch will not be performed. If the value is set to 60, then its guaranteed that a background fetch will not be performed for the next hour. However, the fetch may take place at any time after that. This option is not so much of an "interval" as a hint to iOS about the application's data constraints. During the launch at WWDC, Apple gave the example of a weather application that polls personal weather stations. Because polling these stations is an expensive operation, the backend server was configured to only poll once per hour. Thus, fetching more frequently than once per hour would be a waste of user battery life and server resources.

Implementing Background Fetch

There are three steps to using the Background Fetch API in an application.

1. Enable the Background Fetch background mode capability.

2. Enable fetching by configuring a fetch interval

3. Implement the new UIApplicationDelegate application:performFetchWithCompletionHandler: callback

Apple launched Xcode 5 along with iOS 7 at WWDC 2013, which makes the first step trivial. Go to the new Capabilities pane for your project, enable "Background Modes" and check the box next to "Background fetch". As you can see from the note in the image below, this automatically completes the step of adding the required background modes to the info plist file for you.

The step to enable fetching is equally easy, requiring only a single line of code. Now that we understand the various background fetch intervals, we can select the recommended interval of UIApplicationBackgroundFetchIntervalMinimum for our example BepBop app. Because our demo won't be displaying content tailored for users, we simply set the fetch interval in the standard application:didFinishLaunchingWithOptions: delegate callback.

0001:- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
0002:{
0003: if (IS_IOS_7) {
0004: [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
0005: // other iOS 7 specific setup ...
0006: }
0007: // more setup ...
0008: return YES;
0009:}

Note that we've made this call behind a simple IS_IOS_7 macro, defined as:

#define IS_IOS_7 ([[[UIDevice currentDevice] systemVersion] floatValue] > 6.10001:

This allows us to support both iOS 7 and lesser versions. And yes, we mean that in a derogatory manner. Alternatively, you could check for the existence of the setMinimumBackgroundFetchInterval method on the UIApplicationclass.

The final step is to actually implement the background fetch. Recall that the demo app that we're developing simply displays images of random animals. Its Hot-or-Not Animal Farm Edition. We start by implementing the UIApplicationDelegate application:performFetchWithCompletionHandler: callback.

0001:-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
0002:{
0003: [[BEPBackgroundDownloadHandler sharedInstance] refreshWithCompletionHandler:^(BOOL didReceiveNewImage, NSError *error) {
0004: if (error) {
0005: completionHandler(UIBackgroundFetchResultFailed);
0006: } else if (didReceiveNewImage) {
0007: [UIApplication sharedApplication].applicationIconBadgeNumber++;
0008: completionHandler(UIBackgroundFetchResultNewData);
0009: } else {
0010: completionHandler(UIBackgroundFetchResultNoData);
0011: }
0012: }];
0013:}

Its critically important to always call the background fetch completion handler. iOS will terminate any app which fails to do so. This is also the opportunity to update the snapshot for presentation in the App Switcher and to setup the state for next launch by the user. iOS saves the state restoration archive immediately after the completion handler is called.

An easy rule to ensure that the completion handler is invoked for each performFetchWithCompletionHandler operation is to never store the completion handler; pass it down the call stack to the location at which it will be invoked. If the completion handler is stored and another path also requests a refresh before the first is complete, the original completion listener may be overwritten and thus never called. So just don't store it.

There are three possible completion handler statuses: UIBackgroundFetchResultNewData, UIBackgroundFetchResultNoData, and UIBackgroundResultFailed. The completion handler should always be called with the proper status. This gives iOS information about the operation of the app. This is presumably used in deciding the next interval in which to perform a background fetch.

That's all that is required for enabling an app to perform background fetches. We can now see the application icon badge increment as background fetches occur and new content is accumulated.

However, testing requires two different mechanisms because there are two scenarios under which performFetchWithCompletionHandler may be called depending on whether the application is already running. If the application is not running, it will be launched. Otherwise, iOS will simply resume the inactive application. Each of these scenarios is tested differently.

The easiest way to test the launch case is to create a new scheme. Go to manage schemes:

Then duplicate the existing scheme:

And now enable the "Launch due to a background fetch event" option under the Options panel:

When this scheme is activated and run, it will launch a new instance of the app.

For testing the resume case, Apple has added a new menu option to Xcode 5: "Simulate Background Fetch". It can be found under the Debug menu.

When the application is running, selecting this menu option will put the app into the background and then trigger the background fetch. In our testing, we've found a few bugs in the interaction between Background Fetch and the Background Transfer Service when this option is used and the app is not in the foreground. This will be further discussed below.

Remote Notifications


Apple provided a substantial improvement to its Remote (Push) Notifications API in iOS 7. Unfortunately, due to the nature and scope of this ebook, we won't be able to cover remote notifications in depth. Most of the tutorials on iOS 6 remote notifications still apply. We will try to provide pointers to get you started where this differs from iOS 7.

In previous versions iOS immediately displayed the alert to the user. Only when the user decided to open the app ("slide to view" on the alert) did the app have an opportunity to update itself. This left the user staring at the previous content and waiting for the content for which he or she was alerted to appear. In iOS 7 developers have the ability to receive remote notifications in the background and refresh its content before the user receives the push alert.

iOS 7 also provides developers with the exciting new ability to send a Silent Remote Notification. This type of notification will wake up an app on a remote device in the background to perform some work which doesn't require immediate user attention. This is performed by sending a specially-crafted push message to the device which contains instructions not to alert the user.

The first step to receiving Remote Notifications in an app is to enable the new "remote notifications background mode capability. This capability is enabled in the same way as the "background fetch" capability shown in the previous section.

Several new delegate methods were added to the UIApplicationDelegate object. The most important of these for this purpose is application:didReceiveRemoteNotification:fetchCompletionHandler:. Much like the performFetchWithCompletionHandler delegate method we explored earlier, this delegate offers apps the opportunity to fetch new data in response to an incoming remote notification. And like this delegate method, you must call the completion handler as soon as the operation is complete so that iOS is able to better manage the device energy and data usage. After invoking the completion handler, iOS may display an alert to the user prompting them to visit the newly-refreshed app.

Background Transfer Service


The Background Transfer Service allows the developer to enqueue large uploads and downloads for iOS to complete in the background. The Background Transfer Service is useful for files or data transfers that are too large to expect the user to wait for completion or risk interruption if the user navigates away from the app. Apple introduced this API to alleviate network connectivity challenges due to non-contiguous execution in the new iOS 7 background task model discussed previously. Pending background transfers are persisted across network interruptions, application terminations, and even user device restarts. This API transforms a previously-challenging problem into a trivial one for third-party developers.

This ease-of-use comes at a price, however. There are two types of transfers:

· Discretionary transfer: helps preserve the user's data and energy

· Non-discretionary transfer: prioritized for the user's attention

Discretionary transfers are power-managed and only take place when the user has a WiFi connection. (Apple doesn't tell us if there's a threshold power level at which all discretionary transfers will be paused.) All transfers requested by an app in the background are discretionary, and the app can optionally request a transfer be discretionary when requesting the transfer from the foreground. Most background transfers should be discretionary, even if initiated from the foreground, unless the user is actively waiting on the transfer to complete.

Apple introduced a new class, NSURLSession, which serves as an entry-point to the Background Transfer Service API. A NSURLSession manages an associated queue of NSURLSessionTasks. Each task is responsible for completing a single NSURLRequest.

There are several types of tasks available to you as subclasses of NSURLSessionTask:

· NSURLSessionUploadTask: used to upload a file or data object

· NSURLSessionDownloadTask: used to download a file or data object to a temporary location

· NSURLSessionDataTask: most commonly used for downloading data for parsing as a stream

The example BepBop app demonstrates usage of the Background Transfer Service by initiating an image download from a Background Fetch (see previous section) using the NSURLSessionDownloadTask. This allows the app to periodically be woken to check for new updates and download images in the background, so that the user is immediately greeted with large new images after launching the app. There's nothing more engaging than that!

Getting started with the Background Transfer Service API is easy. First we must create a new NSURLSession. We use a helper function to ensure that multiple background sessions with the same identifier are not created. An application may have multiple background sessions within a single process but each session must have a unique identifier.

0001:- (NSURLSession *)backgroundSession
0002:{
0003: static NSURLSession *session = nil;
0004: static dispatch_once_t onceToken;
0005: dispatch_once(&onceToken, ^{
0006: NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.bleedingedgepress.iosedge"];
0007: session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
0008: });
0009: return session;
0010:}
0011:

This method calls dispatch_once from Grand Central Dispatch (GCD) to create the background session as a singleton instance.

Next we create a download task from this session. NSURLSession exposes factory methods for creating the various session tasks. In the BepBop example app, we use downloadTaskWithURL: to create an NSURLSessionDownloadTask. However, during our testing, we discovered a bug that results in this factory method sporadically returning nil under certain conditions (such as being invoked while the app is in the background). Because this method is not documented to return nil and its occurring non-deterministically, we worked around the problem by adding a retry loop.

0001:- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)downloadURL
0002:{
0003: static int numRetries = 10;
0004: NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:downloadURL];
0005: for (int i = 0; downloadTask == nil && i < numRetries; i++) {
0006: downloadTask = [self.session downloadTaskWithURL:downloadURL];
0007: }
0008: return downloadTask;
0009:}
0010:

This loop iterates until a non-nil task is returned. To prevent the possibility of an infinite loop, there are a limited number of retries allowed (10 in this example). We can use this helper as follows:

0001: NSURLSessionDownloadTask *downloadTask = [self downloadTaskWithURL:imageUrl];
0002: if (downloadTask == nil) {
0003: completionHandler(NO, [NSError errorWithDomain:@"Unable to get download task" code:1 userInfo:nil]);
0004: }
0005: [downloadTask resume];
0006:

Note that its still possible to return nil if we don't receive a valid download task within the allotted number of retires. We check for this case and pass an error to the completion handler to inform the caller. Now we must start the download task. NSURLSessionTask objects are always in a suspended state after creation and must be sent the resume message to begin execution.

To receive the downloaded file, we must implement the necessary delegate protocol. There are three protocols necessary to use to use the NSURLSessionDownloadTask: NSURLSessionDelegate, NSURLSessionTaskDelegate, and NSURLSessionDownloadDelegate. Fortunately, only the NSURLSessionDownloadDelegate has required methods for us to implement. For our purposes, the most important of these delegate methods is didFinishDownloadingToURL. This message is sent when a download task has completed to give us the opportunity to copy or move the download file from the temporary location to a permanent location in our sandboxed container. Otherwise the download will be deleted when the delegate message returns.

0001:- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL
0002:{
0003: // Copy the file from the downloadURL to the Documents directory of our app.
0004: NSFileManager *fileManager = [NSFileManager defaultManager];
0005: NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0];
0006: NSURL *destinationURL = [documentsDirectory URLByAppendingPathComponent:[[[downloadTask originalRequest] URL] lastPathComponent]];
0007:
0008: // We don't care if we've already downloaded this file. Just remove it.
0009: NSError *error;
0010: [fileManager removeItemAtURL:destinationURL error:NULL];
0011: BOOL success = [fileManager copyItemAtURL:downloadURL toURL:destinationURL error:&error];
0012:
0013: if (success) {
0014: dispatch_async(dispatch_get_main_queue(), ^{
0015: UIImage *image = [UIImage imageWithContentsOfFile:[destinationURL path]];
0016: // do something with the downloaded image - store it, update the UI, send a notification, etc.
0017: });
0018: } else {
0019: NSLog(@"Error copying the downloaded file: %@", [error localizedDescription]);
0020: }
0021:}
0022:

This method might look intimidating, but it's mostly boilerplate file system operations. The download location is passed in as a NSURL object. We use the filename from the original request to determine the filename it should have within the sandboxed Documents directory for our app. In the case of an existing file at this location in the example BepBop app, we chose to "overwrite" the existing files by removing any existing file before performing the copy. Other apps might prefer to treat this as an error condition or take an alternative action. After removing any existing file, we simply copy the file from the temporary location to its final destination. If the copy is successful, we can do something with the download. As always, any UI changes must be performed on the main thread. We use the familiar dispatch_async(dispatch_get_main_queue(), ... ) method to make such changes.

With this change we can now see results of the Background Transfer as initiated by a Background Fetch or from the user manually creating it via Pull to Refresh.

The final piece is to implement the new UIApplicationDelegate callback handleEventsForBackgroundURLSession. This delegate message will be received when the app is launched or resumed to handle the completion of tasks for a background session. A completion handler is provided to notify iOS about the operations of the background sessions in the app, much like for the Background Fetch API.

0001:- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
0002:{
0003: self.backgroundSessionCompletionHandler = completionHandler;
0004:}
0005:

Unlike the fetch API, however, it's safe to call this completion handler multiple times. Additionally, because the example BepBop app only has a single background session, its okay to store the completion handler in a property instead of maintaining an identifier to handler association.

This completion handler will be invoked by the NSURLSessionDelegate's URLSessionDidFinishEventsForBackgroundURLSession method. Handling this message is optional, but enables us to call this completion handler and signal back to iOS that all background tasks have been processed.

Summary

In this chapter we have introduced the new multitasking APIs available in iOS 7 and walked through examples of the Background Fetch and Background Transfer Service APIs. These new APIs give your application a chance to perform actions in the background, such as completing a database transaction, encoding a video, or downloading a large file, without putting undue strain on the user's battery or data connection. We've used these APIs to show how to build an app that refreshes its content with large, beautiful images before the user opens it. As discussed, featuring fresh, relevant content in a snappy application is one of the best ways to engage users, a critical activity for most businesses.