Update note: This tutorial was updated to iOS 10 and Swift 3 by Chris Wagner. This marks the third revision; original post by Gustavo Ambrozio and second revision by Ray Fix.
Way back in 2010 with iOS 4, Apple introduced multitasking.
Developers and users alike are often confused as to what the iOS multitasking system allows. Apple has restrictions in place for the use of background operations in an effort to improve user experience and extend battery life. Your app is only allowed to keep running in the background in very specific cases. For example, these include playing audio, getting location updates, or fetching the latest content from a server.
If your task does not fall into these categories backgrounding may not be for you. You may even find yourself with an App Store rejection if you try to cheat the system by using background modes outside the realm of their purposes, so consider yourself warned! :]
Getting Started
Before digging into the project, here’s a quick overview of the basic background modes available in iOS. In Xcode 8, you can see the list by pulling up the Capabilities tab of your app target. It looks like this:
To get to the background modes capability list you (1) select the project from the Project Navigator, (2) select the app target, (3) select the Capabilities tab, and (4) turn the Background Modes switch on.
In this background modes tutorial you will investigate four ways of doing background processing.
- Play audio: the app can continue playing and/or recording audio in the background.
- Receive location updates: the app can continue to get callbacks as the device’s location changes.
- Perform finite-length tasks: the generic “whatever” case, where the app can run arbitrary code for a limited amount of time.
- Background Fetch: Get updates to the latest content scheduled by iOS.
If you are only interested in one or several of these modes, feel free to skip around!
To get started download the starter project. You’ll find the user interface and some of the logic that is not directly applicable to the topic has been provided for you.
Before you can run the project you must set your development team, as shown here:
Run the sample project to get a feel for it. There are 4 tabs; one to cover each mode:
Note: For the full effect, you should follow along on a real device. In my experience, if you forget a configuration setting, the app might appear to run fine in the background on the Simulator. However, when you switch to a real device, it doesn’t work at all.
Playing Audio
First up, background audio.
There are several ways to play audio on iOS and most of them require implementing callbacks to provide more audio data to play.
If you want to play audio from streaming data, you can start a network connection, and the connection callbacks provide continuous audio data.
When you activate the audio background mode, iOS will continue these callbacks even if your app is not the current active app. That’s right – the audio background mode is virtually automatic. You just have to activate it and provide the infrastructure to handle it appropriately.
In this section you’ll review the audio player in the app, verify that background mode is not working, enable the audio background capability, and demonstrate to yourself that it’s working.
Open AudioViewController.swift and take a look around.
The app makes use of an AVQueuePlayer
to queue up songs and play them one after the other. The view controller is observing the player’s currentItem
value to provide updates.
The starter project includes audio files from incompetech.com, a favorite royalty-free music website. With credit given, you can use the music for free. So, here you go: all songs are by Kevin MacLeod at incompetech.com. Thanks, Kevin!
When the app is in the active state, the music title label is shown and when in the app is in the background it will print the title to the console. The label text could still update while in the background, but the point here is just to demonstrate that your app continues to receive this callback when your app is in the background.
Note: Check out the Execution States for Apps to learn more about the active state and others.
Build and run, and you should see this:
Now hit Play and the music will start. Nice!
Test if backgrounding works. While running on an actual device hit the home button and the music will stop. Why? Well, there’s still a very crucial piece missing!
For most background modes (the “Whatever” mode is the exception) you need to enable the capability to indicate that the app wants to run code while in the background.
Back in Xcode, do the following:
- Click the project in the Project Navigator;
- Click TheBackgrounder target;
- Click on the Capabilities tab;
- Scroll down to Background Modes and turn the switch on;
- Check Audio, AirPlay and Picture in Picture.
Build and run again. Start the music and hit the home button, and this time you should still hear the music, even though the app is in the background.
You should also see the time updates in your Console output in Xcode, proof that your code is still working even though the app is in the background.
Wow, if you already have an audio player in place, playing audio in the background is easy! Well, that’s one mode down, if you’re following through the entire background modes tutorial – three to go!
Receiving Location Updates
When in location background mode, your app will still receive location delegate messages with the updated location of the user, even when the app is in the background. You can control the accuracy of these location updates and even change that accuracy while backgrounded.
The second tab is for location updates, so open LocationViewController.swift and take a look around. Similarly to the background audio example, the location background mode is very easy to get up and running with if you already have location features working!
In this controller you will find that CLLocationManager
runs the show. To receive location information you create and configure a CLLocationManager
instance. In this case the app monitors location when an on screen UISwitch
is activated. As location updates are received the app draws pins on a map. When the app is in the background, you should see the log of location updates in your console output in Xcode.
An important line to note is the call to requestAlwaysAuthorization()
on the CLLocationManager
instance. This is a requirement since iOS 8, and brings up a dialog asking for permission to receive locations in the background.
Now that you’re familiar with Background Modes, you don’t need to make the same mistake as before! Check the box for Location updates to let iOS know that your app wants to continue receiving location updates while in the background.
In addition to checking this box, iOS 8 and above requires that you set a key in your Info.plist explaining to the user why background updates are needed. If you do not include this, location requests will silently fail.
- Select the project in Xcode.
- Select the Info tab for TheBackgrounder target.
- Select an existing row.
- Click on the + button to add a new key.
- Add the key named Privacy – Location Always Usage Description.
- Write a convincing description of why you need to receive location updates in the background.
Now build and run! Switch to the second tab and flip the switch to ON.
When you do this the first time, you will see the message that you put into your location privacy reason. Tap Allow and go for a walk outside or around your building (try not to get too distracted catching Pokemons). You should start seeing location updates, even on the Simulator.
After a while, you should see something like this:
If you background the app, you should see the app updating the location in your console log. Open the app again and you’ll see that the map has all the pins for the locations that were updated while the app was in the background.
If you’re using the Simulator, you can use it to simulate movement, too! Check out the Debug \ Location menu:
Try setting the location to Freeway Drive and then hit the home button. You should see the console logs printing out your progress as you simulate that drive down a California highway:
App is backgrounded. New location is %@ <+37.33500926,-122.03272188> +/- 5.00m (speed 7.74 mps / course 246.09) @ 9/5/16, 10:20:07 PM Mountain Standard Time App is backgrounded. New location is %@ <+37.33500926,-122.03272188> +/- 5.00m (speed 7.74 mps / course 246.09) @ 9/5/16, 10:20:07 PM Mountain Standard Time App is backgrounded. New location is %@ <+37.33500926,-122.03272188> +/- 5.00m (speed 7.74 mps / course 246.09) @ 9/5/16, 10:20:07 PM Mountain Standard Time |
Easy peasy, right?! On to the third tab and the third background mode!
Performing Finite-Length Tasks… or, Whatever
The next background mode is officially called Executing a Finite-Length Task in the Background. What a mouthful. Whatever is a bit catchier!
Technically, this is not a background mode at all, as you don’t have to declare that your app uses this mode in Capabilities. Instead, it’s an API that lets you run arbitrary code for a finite amount of time when your app is in the background. Well… whatever!
In the past, this mode was often used to complete an upload or download and a generous (but not guaranteed) 10 minutes was provided to accomplish these tasks. But what if the connection was slow and the process still didn’t finish? It would leave your app in an odd state and you would have to add lots of error handling code to make things work sanely. Because of this, Apple introduced NSURLSession
.
Although not a topic of this background modes tutorial, NSURLSession
is robust in the face of backgrounding and even device reboots and accomplishes this in a power efficient way. If you are dealing with large downloads, check out our NSURLSession tutorial.
A still very valid use case of the Whatever background mode is to complete some long running task, such as rendering and writing a video to the camera roll.
But this is just one example. As the code you can run is arbitrary, you can use this API to do pretty much anything: perform lengthy calculations (as in this background modes tutorial), apply filters to images, render a complicated 3D mesh… whatever! Your imagination is the limit, as long as you keep in mind that you only get some time, not unlimited time.
How much time you get after your app gets backgrounded is determined by iOS. There are no guarantees on the time you’re granted, but you can always check backgroundTimeRemaining
on UIApplication
. This will tell you how much time you have left.
The general, observation-based consensus is that you get three minutes. Again, there are no guarantees and the API documentation doesn’t even give a ballpark number – so don’t rely on this number. You might get 5 minutes or 5 seconds, so your app needs to be prepared for… whatever!
Here’s a common task that every CS student should be familiar with: calculating numbers in the Fibonacci Sequence. The twist here is that you’ll calculate these numbers in the background!
Open WhateverViewController.swift and take a look at what’s there already. As it stands, this view will calculate Fibonacci numbers in sequence and display the result. If you were to suspend the app on an actual device, the calculations would stop and pickup where they were once the app was active again. Your task is to create a background task so that the calculation can keep running until iOS says, ‘No more!’.
You first need to add the following property to WhateverViewController
.
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid |
This property is used to identify the task request to run in the background.
Next add the following methods to WhateverViewController
.
func registerBackgroundTask() { backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in self?.endBackgroundTask() } assert(backgroundTask != UIBackgroundTaskInvalid) } func endBackgroundTask() { print("Background task ended.") UIApplication.shared.endBackgroundTask(backgroundTask) backgroundTask = UIBackgroundTaskInvalid } |
registerBackgroundTask()
tells iOS that you need more time to complete whatever it is you’re doing in case the app is backgrounded. After this call, if your app is backgrounded it will still get CPU time until you call endBackgroundTask()
.
Well, almost. If you don’t call endBackgroundTask()
after a period of time in the background, iOS will call the closure defined when you called beginBackgroundTask(expirationHandler:)
to give you a chance to stop executing code. So it’s a very good idea to then call endBackgroundTask()
to tell the OS that you’re done. If you don’t do this and continue to execute code after this block is called, your app will be terminated!
Now for the important part, you need to update didTapPlayPause(_:)
to register the background task and end it. There are two comments in this method where you will add some code.
Call registerBackgroundTask()
below the “register background task” comment.
// register background task registerBackgroundTask() |
registerBackgroundTask()
is now called when calculations begin so you can continue calculating numbers in the background.
Now add the following block below the “end background task” comment.
// end background task if backgroundTask != UIBackgroundTaskInvalid { endBackgroundTask() } |
Now when calculations are stopped by the user endBackgroundTask()
is called to indicate to iOS that you don’t need any extra CPU time.
It is important that you call endBackgroundTask()
for every time you call beginBackgroundTask(expirationHandler:)
. If you call beginBackgroundTaskWithExpirationHandler(_:)
twice and only call endBackgroundTask()
for one of the tasks, you’re still going to get CPU time until you call endBackgroundTask()
a second time with the value of the second background task. This is also why you needed backgroundTask
.
Now update the calculateNextNumber()
method to behave differently based on the application’s state.
Replace the final line, resultsLabel.text = resultsMessage
, in the method with the following:
switch UIApplication.shared.applicationState { case .active: resultsLabel.text = resultsMessage case .background: print("App is backgrounded. Next number = \(resultsMessage)") print("Background time remaining = \(UIApplication.shared.backgroundTimeRemaining) seconds") case .inactive: break } |
The label will only be updated when the application is active. When the application is backgrounded a message will be printed to the console instead, stating what the new result is and how much background time is remaining.
Build and run, then switch to the third tab.
Tap Play and you should see the app calculating the values. Now hit the home button and watch the output in Xcode’s console. You should see the app still updating the numbers while the time remaining goes down.
In most cases, this time will start with 180 (180 seconds = 3 minutes) and go down to 5 seconds. If you wait for the time to expire when you reach 5 seconds (could be another value depending on your specific conditions), the expiration block will be invoked and your app should stop outputting anything. Then if you go back to the app, the timer should start firing again and the whole madness will continue.
There’s only one bug in this code. Suppose you background the app and wait until the allotted time expires. In this case, your app will call the expiration handler and invoke endBackgroundTask()
, thus ending the need for background time.
If you then return to the app, the timer will continue to fire. But if you leave the app again, you’ll get no background time at all. Why? Because nowhere between the expiration and returning to the background did the app call beginBackgroundTaskWithExpirationHandler(_:)
.
How can you solve this? There are a number of ways to go about it and one of them is to use a state change notification.
You can see all the details on how to respond to state change in Apple’s documentation for App States for Apps.
Time to fix the bug. First, add a new method named reinstateBackgroundTask()
.
func reinstateBackgroundTask() { if updateTimer != nil && (backgroundTask == UIBackgroundTaskInvalid) { registerBackgroundTask() } } |
You only need to reinstate if there is a timer running and the background task is invalid. Breaking your code into smaller utility functions that do one thing is paying off. In this case you simply need to call registerBackgroundTask()
.
Now override viewDidLoad()
and subscribe to UIApplicationDidBecomeActiveNotification
by adding the following:
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(reinstateBackgroundTask), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil) } |
This designates your new method reinstateBackgroundTask()
to be called whenever the application becomes active.
Whenever you subscribe to a notification you should also think about where to unsubscribe. Use deinit
to do this. Add the following:
deinit { NotificationCenter.default.removeObserver(self) } |
And there you have it, you can now do whatever you want, at least for as long as iOS says that it is okay.
On to the final topic for this background modes tutorial: Background Fetching.
Background Fetch
Background fetch is a mode introduced in iOS 7 that lets your app appear always up-to-date with the latest information while minimizing the impact on battery. Suppose, for example, you were implementing a news feed in your app. Prior to background fetch, you might do this by getting new data in viewWillAppear(_:)
.
The problem with this solution is that your user is looking at the old data for at least several seconds until the new data comes in. Wouldn’t it be better if right when they opened your app the new data was magically there? This is what background fetch gives you.
When enabled, the system uses usage patterns to determine when to best fire off a background fetch. For example, if your user opens the app at 9 AM each morning, it is likely that a background fetch will be scheduled sometime before that time. The system decides the best time to issue a background fetch and for this reason you should not use it to do critical updates.
In order to implement background fetch there are three things you must do:
- Check the box Background fetch in the Background Modes of your app’s Capabilities.
- Use
setMinimumBackgroundFetchInterval(_:)
to set a time interval appropriate for your app. - Implement
application(_:performFetchWithCompletionHandler:)
in your app delegate to handle the background fetch.
As the name implies, background fetch normally involves fetching information from an external source like a network service. For the purposes of this background modes tutorial, you won’t use the network but just fetch the current time. This simplification will let you understand everything required to perform a background fetch and test it without worrying about an external service.
In contrast to finite-length tasks, you only have seconds to operate when doing a background fetch – the consensus figure is a maximum of 30 seconds, but shorter is better. If you need to download large resources as part of the fetch, this is where you need to use NSURLSession
‘s background transfer service.
Time to get started. First, open FetchViewController.swift and take a look at what this does, there’s not much to it.
The fetch(_:)
method is a simplified replacement of what you might actually do to fetch some data from an external source (such as a JSON or XML RESTful service). Since it might take several seconds to fetch and parse the data, you pass a completion handler that gets called when the process is done. You will see why this is important a little later.
updateUI()
formats the time and shows it. The guard
statement around updateLabel
is to ensure that the view has actually been loaded. time
is an optional type so if it is not set, it shows the message “Not updated yet”.
When the view is first loaded (in viewDidLoad()
) you don’t fetch, but go straight to updateUI()
which means the message “Not yet updated” will appear first. Finally, when the Update button is tapped it performs a fetch and as a completion updates the UI.
The view controller is working. There’s nothing you need to do to it.
However, background fetching is not enabled.
The first step to enabling background fetching is to check Background fetch in the Capabilities tab of your app. By now this should be old hat. Go ahead and do this.
Next, open AppDelegate.swift add the following to application(_:didFinishLaunchingWithOptions:)
:
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum) |
This requests background fetches by setting the minimum background fetch interval. The default interval is UIApplicationBackgroundFetchIntervalNever
, which you might want to switch back to, if, for example, your user logs out and no longer needs updates. You can also set a specific interval in seconds. The system will wait at least that many seconds before issuing a background fetch.
Be careful not to set the value too small because it may chew through battery unnecessarily as well as hammer your server. In the end the exact timing of the fetch is up to the system but will wait at least this interval before performing it. Generally, UIApplicationBackgroundFetchIntervalMinimum
is a good default value to use.
Finally, to enable background fetch you must implement application(_:performFetchWithCompletionHandler:)
. Add that now to AppDelegate
:
// Support for background fetch func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { if let tabBarController = window?.rootViewController as? UITabBarController, let viewControllers = tabBarController.viewControllers { for viewController in viewControllers { if let fetchViewController = viewController as? FetchViewController { fetchViewController.fetch { fetchViewController.updateUI() completionHandler(.newData) } } } } } |
First you need to get the FetchViewContoller
. Next, optionally cast since the rootViewController
is not necessarily a UITabBarController
in every app, although it is in this app so it will never fail.
Next, you loop though all of the view controllers in the tab bar controller and find the one that successfully casts to FetchViewController
. In this app you know it is the last view controller so you could hard-code it, but looping makes it a little more robust in case you decide to add or remove a tab later.
Finally you call fetch(_:)
. When it completes, you update the UI and then call the completionHandler
that was passed in as a parameter of the function. It is important that you call this completion handler at the end of the operation. You specify what happened during the fetch as the first parameter. Possible values are .newData
, .noData
, or .failed
.
For simplicity, the tutorial always specifies .newData
since getting the time will never fail and is always different than the last call. iOS can then use this value to better time the background fetches. The system knows at this point to take a snapshot of the app so it can present it in the card view of the app switcher. And that is all there is to it.
Note: Rather than passing the completion closure along, it can be tempting to save it away in a property variable and call that when your fetch completes. Don’t do this. If you get multiple calls to application(_:performFetchWithCompletionHandler:)
, the previous handler will get overwritten and never called. It is better to pass the handler through and call it as it will make this kind of programming error impossible.
Testing Background Fetch
One way to test background fetch is to sit around and wait for the system to decide to do it. That would require a lot of sitting. Fortunately, Xcode gives a way to simulate a background fetch. There are two scenarios that you need to test. One is when your app is in the background, and the other when your app is coming back from being suspended. The first way is the easiest and is just a menu selection.
- Start the app on an actual device (not a simulator);
- Go to the Fetch tab
- Notice the message is “Not yet updated”
- From the Debug menu in Xcode, select Simulate Background Fetch;
- The app will be sent to the background and Xcode’s debugger will trap. Instruct the debugger to continue
- Reopen the app
- Notice that the time is updated!.
The other way you should test background fetch is to do it as a resume from suspension. There is a launch option that lets you launch your app directly into suspension. Since you might want to test this semi-often it is best to make a new Scheme with that option always set. Xcode makes this easy.
First select the Manage Schemes option.
Next, select the only scheme in the list, and then click on the gear icon and select Duplicate.
Lastly, rename your scheme to something reasonable, like, “Background Fetch” and check the checkbox to Launch due to a background fetch event.
Run your app with this scheme. You will notice that the app never actually opens but is launched into a suspended state. Now manually launch it and go to the Fetch tab. You should see that there is an updated time when you launched the app instead of “Not yet updated”.
Using background fetch effectively lets your users to seamlessly get the latest content.
Where to Go From Here?
You can download the fully finished sample project here.
If you want to read Apple’s documentation on what we covered here for background modes, the best place to start is in Background Execution. This documentation explains all background modes and has links to the appropriate section of the documentation for each one.
A particularly interesting section of this document is the one that talks about being a responsible background app. There are some details that might or might not relate to your app here that you should know before releasing an app that runs in the background.
Finally, be sure to check out NSURLSession if you plan on doing large network transfers in the background.
We hope you enjoyed this tutorial on background modes, and if you have any questions or comments, please join the forum discussion below!
The post Background Modes Tutorial: Getting Started appeared first on Ray Wenderlich.