For iOS 9 apps that depend on real-time information, location services, or communication with external devices, developers can use background execution to improve user experience by allowing the app to perform tasks even when the app isn’t in the foreground. Performing network requests in the background can be especially helpful when downloading or uploading large amounts of data.

iOS restricts apps from running indefinitely in the background, and for good reasons. If an app isn't active, then it shouldn't be allowed to use an unreasonable amount of system resources, especially when it comes to data transfer. But as apps have become more connected to backend services, fetching data in the background has become more important for a good user experience.

Unfortunately, it isn’t always clear what’s the best way to implement background network requests. The latest iOS SDK provides multiple options, and familiarity with different background fetch APIs is useful when deciding which technique to use. Because runaway background tasks can result in a massive drain on device battery life and difficult-to-reproduce behavior, correct implementation of iOS background execution APIs is important. This post offers an introduction to the issues, and examines some common pitfalls.

Understanding iOS app execution states

Most iOS users are familiar with the multitasking UI in iOS 9, which brings up a list of recently used apps when you double-tap the Home button. Swiping up on an app will then cause the operating system to force quit that app. However, apps displayed in the multitasking UI aren't necessarily executing code or fetching data. Listed apps may be suspended or not running at all (a fact that has long confused iOS users in search of a trick to prolong battery life).

ios 9 screen
iOS 9 Multitasking Interface

 

Using Swift, the current execution state of an app is available from

UIApplication.sharedApplication().applicationState

If the state is active, the application is visible on the screen and ready to receive events. When it’s not visible the app can be in the background or inactive. Apple has a nice diagram of all of the states on its developer site.

Most developers respond to state changes using delegate methods on UIApplication or a dozen-plus notification types that are posted. The Xcode 7 iOS template includes these delegate methods for responding to changes:

// App is almost ready to run

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool

// App is going from active to inactive

func applicationWillResignActive(application: UIApplication)

// Background mode was just activated

func applicationDidEnterBackground(application: UIApplication)

// App is now visible and ready to receive UI events

func applicationDidBecomeActive(application: UIApplication)

// App is about to terminate

func applicationWillTerminate(application: UIApplication)

Nothing exciting happens by default when the application enters the background—it’s merely a brief transition the app passes through on the way to being suspended. Background state can even be disabled (although that is discouraged by Apple). While there are several different use cases for background execution, including communication with Bluetooth devices and playing audio, many apps use background execution for downloading remote content.

Downloading and uploading in the background with NSURLSession

When an app is working with data that needs to be uploaded or downloaded, the operation should ideally continue if the user sends a text message or switches to a different app. Fortunately, the NSURLSession class can hand off downloads and uploads to the operating system when the app becomes inactive. As with almost all background execution APIs, if the user force quits from the multitasking UI, the background operation will terminate. (Note that if an app is tracking location and is force quit by the user, it will be relaunched.)

Enabling NSURLSession to be background-capable requires instantiating a NSURLSessionConfiguration object with the background initializer and an identifier that’s reused for all background sessions:

let sessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.newrelic.bgt")

If an app is especially polite, there is a flag on NSURLSessionConfiguation called “discretionary” that allows iOS to optimize the request for performance, so in certain situations (like a bad connection with a low battery) the request never actually occurs.

backgroundSessionConfig.discretionary = true

As long as the app is making an HTTP or HTTPS request, a NSURLSession needs to be instantiated with the configuration object and a delegate to receive notifications when a download or upload completes. There are a few other limitations outlined here:

let session = NSURLSession(configuration: backgroundSessionConfig, delegate: self, delegateQueue: NSOperationQueue.mainQueue())

To download a static PDF file, for example, the session with the background configuration can then be used in a standard download task:

let downloadTask = session.downloadTaskWithURL(NSURL(string: "https://try.newrelic.com/rs/newrelic/images/nr_getting_started_guide.pdf")!)

downloadTask.resume()

The NSURLSession delegate methods get called when the operation is complete or if it had errors. There will be a path to a temporary file on-disk that can be opened for reading or moving to another location.

One last thing about NSURLSession: it has supported HTTP/2 since iOS 9. More details on using the API are available on Apple’s developer site.

Downloading remote content opportunistically

In iOS 7, Apple added support for background fetch—a kind of smart, per-app crontab that wakes up at opportunistic times. There is no way to force background fetches to execute at exact intervals. iOS checks how much data and battery power was used during previous background fetches when scheduling future callbacks.

Adding support involves editing your application’s property list (see UIBackgroundModes) and setting a fetch interval early in the app lifecycle:

application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)

This UIApplicationDelegate method gets called when iOS decides a background fetch can happen:

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void)

The method has approximately 30 seconds to return a UIBackgroundFetchResult to the completionHandler function before the app is terminated. The value of UIBackgroundFetchResult is used to determine when the background fetch delegate method gets called again. If there’s frequently data at a particular time (for example, in the early morning for a news app), this helps iOS know when to execute the background fetch:

enum UIBackgroundFetchResult : UInt { case NewData case NoData case Failed}

Background fetches can also be triggered by remote push notification and have a very similar delegate method with the same completion handler.

To test a background fetch event in the iOS Simulator, Xcode has a “Launch due to background fetch event” toggle under options in the Scheme editor and a “Simulate Background Fetch” item under the debug menu. It will invoke the performFetchWithCompletionHandler method as shown here:

xcode example
Enabling “Launch due to background fetch event” in Xcode.

 

As of early 2016, developers noted issues with using the iOS simulator to test background fetch, so testing on real devices with a clean install of the app is preferred. Additionally, the Xcode debugger changes how the operating system suspends the app and may also cause issues when testing inactive states. Testing on a device without the debugger attached (exactly as your users would interact with your app) is sometimes the only way to reliably reproduce certain states.

App termination special cases

The user force quitting from the multitasking UI can be the source of unreproducible crashes. It’s possible for an app to be killed without any notification at all. For example, if the app is suspended and the operating system terminates it due to low memory, no notification will be sent. Only when iOS wants to terminate an app that is not suspended and in the background state will applicationWillTerminate be called.

iOS 9 chart
Different ways an app can be terminated in iOS 9.

 

In iOS 9, apps should not depend on applicationWillTerminate: being called. It’s better to save state and perform cleanup in applicationDidEnterBackground:. However, it’s important to clean up and stop any background tasks that are running if applicationWillTerminate is signaled, as there may be a crash if iOS has to force kill the running background task. This is sometimes a source of difficult-to-reproduce bugs.

For performance and battery life reasons, iOS limits the amount of time in the background. The amount of time remaining in the background execution state is available from

UIApplication.sharedApplication().backgroundTimeRemaining

The amount of backgroundTimeRemaining reported isn’t always guaranteed. Force quitting will stop any background tasks, regardless of how much time is left.

Assumptions about execution state delegate methods always getting called (or even that they will be called in a particular order) may not be borne out in the real world. Carefully audit any code that assumes one execution state will always occur before another state.

Takeaways

When writing iOS code that executes in the background:

  • Determine which background execution API to use. For network requests that take many seconds to complete, NSURLSession is useful. Using opportunistic background fetch provided by iOS to trigger the background fetch delegate is useful for apps that need content on a schedule.
  • Remote push notifications can be an effective mechanism to trigger background code and fetches.
  • Log execution state changes, test on real devices with and without the debugger attached, and beware of simulator quirks. Using an open-source iOS logging library like CocoaLumberjack or XCGLogger can be helpful.
  • Beware of accessing the Keychain or using the iOS Data Protection feature. Background execution can happen when the phone is locked behind a passcode, which can cause issues when reading and writing to protected resources.
  • Performant background code is important: iOS prioritizes the app in the foreground and strictly limits the resources and time an app has to complete background tasks.

With rising mobile data usage and new iOS 9 features like split-view multitasking on iPads, managing app execution state is important to build quality apps—a constant progress indicator when an app opens is guaranteed to annoy users. Background execution and fetch is Apple’s compromise with developers designed to balance customer experience with legitimate concerns about battery drain and high network latency on data networks. Utilizing background fetch APIs to keep information up-to-date and taking care to avoid common pitfalls helps meet customer expectations that apps are always fast, relevant, and never crash.

Additional Resources

Bryce Buchanan, an iOS engineer on New Relic’s Mobile team, contributed to this post with a technical review and invaluable suggestions.

 

Background image courtesy of Shutterstock.com.