47

Background Modes Tutorial: Getting Started [FREE]

 5 years ago
source link: https://www.tuicool.com/articles/hit/ZVBFNfj
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Update note : Brody Eller updated this tutorial for Xcode 10 and Swift 4.2. Chris Wagner wrote the original.

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

You can download the project files at the top or bottom of the tutorial by clicking on the Download Materials button. 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 digging into the project, here’s a quick overview of the basic background modes available in iOS. In Xcode, you can see the list by pulling up the Capabilities tab of your app target. It looks like this:

3QzArmv.png!web

To get to the background modes capability list you:

  1. Select the project from the Project navigator .
  2. Click the app target.
  3. Select the Capabilities tab.
  4. Turn the Background Modes switch on.

In this background modes tutorial, you’ll 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’re only interested in one or several of these modes, feel free to skip around!

Before you can run the project, you must set your development team, as shown here:

m2Y73yq.png!web

Run the sample project to get a feel for it. There are four tabs — one to cover each mode:

7niYBzv.png!web

Note : For the full effect, you should follow along on a real device. 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 may not 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.

Open AudioViewController.swift .

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.

Giving Credit Where Credit Is Due

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:

Thanks, Kevin!

When the app is in the active state, it shows the music title label and, when in the background, it prints 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 Execution States for Apps to learn more about the active state and others.

Build and run, and you should see this:

qiqUjmM.png!web

Now tap Play and the music will start.

Testing Audio in the Background

While running on an actual device, tap 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.

Return to Xcode and do the following:

  1. Click the project in the Project navigator .
  2. Select TheBackgrounder target.
  3. Click on the Capabilities tab.
  4. Scroll down to Background Modes and turn the switch on.
  5. Check Audio, AirPlay and Picture in Picture .

Z7JJfqu.png!web

Build and run again. Start the music and tap the Home button. 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 modes 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 the app runs in the background.

The second tab is for location updates, so open LocationViewController.swift .

In this controller, you’ll 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 the user activates an on-screen UISwitch . As it receives location updates, 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.

Enabling Location Updates

An important line to note is requestAlwaysAuthorization() on the CLLocationManager instance. This is a requirement since iOS 8, and it 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.

i2mIVf7.png!web

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 you need background updates. If you don’t include this, location requests will silently fail. The starter project already has these keys added. To find them:

  • Select the project in Xcode.
  • Click the Info tab for TheBackgrounder target.
  • Look for the keys named Privacy — Location Always and When In Use Usage Description and Privacy — Location When In Use Usage Description .
  • Write a convincing description of why you need to receive location updates in the background.

location-1-650x384.png

Now, build and run! Switch to the second tab and flip the switch on the top left corner to ON .

When you do this the first time, you’ll see the message that you wrote into your location privacy reason. Tap Always 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.

location-2-1-281x500.png

After a while, you should see something like this:

location-3-2-281x500.png

Testing Location Mode in the Background

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:

location-4.png

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!

When to Perform Finite-Length Tasks

A 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 (which is what you’ll do in this 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.

iOS determines how much time you get after your app moves to the background. There are no guarantees on the time you’re granted, but you can always check backgroundTimeRemaining on UIApplication.shared . 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 five minutes or five 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 !

Setting Up a Finite-Length Task

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 pick up where they were once the app became 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 = .invalid

This property identifies 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 != .invalid)
}
  
func endBackgroundTask() {
  print("Background task ended.")
  UIApplication.shared.endBackgroundTask(backgroundTask)
  backgroundTask = .invalid
}

registerBackgroundTask() tells iOS that you need more time to complete whatever it is that you’re doing in case the app moves to the background. After this call, if your app moves to the background, 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 you continue to execute code after this block runs, iOS will terminate your app!

Registering & Ending Background Tasks

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’ll add some code.

Call registerBackgroundTask() below the “register background task” comment:

registerBackgroundTask()

You now call registerBackgroundTask() when calculations begin so you can continue calculating numbers in the background.

Now, add the following block below the “end background task” comment:

if backgroundTask != .invalid {
  endBackgroundTask()
}

Now, when the user stops calculations, you call endBackgroundTask() to indicate to iOS that you don’t need any extra CPU time.

It’s important that you call endBackgroundTask() for every time you call beginBackgroundTask(expirationHandler:) . If you call beginBackgroundTask(expirationHandler:) 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 second background task’s identifier. This is also why you need backgroundTask .

Updating the User Interface

Now update the calculateNextNumber() method to behave differently based on the app’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
}

You update the label only when the app is active. When the app moves to the background, you print a message to the console instead, stating the new result and how much background time remains.

Build and run, then switch to the third tab.

whatever-1-1-281x500.png

Tap Play and you should see the app calculating the values. Now, tap 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 = three minutes) and go down to five seconds. If you wait for the time to expire when you reach five seconds (it could be another value depending on your specific conditions), iOS invokes the expiration block and your app should stop generating output. Then, if you go back to the app, the timer should start firing again and the whole madness will continue.

Handling Timer Expiration With Backgrounding

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 beginBackgroundTask(expirationHandler:) .

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 Execution States for Apps .

Time to fix the bug. First, add a new method named reinstateBackgroundTask() :

@objc func reinstateBackgroundTask() {
  if updateTimer != nil && backgroundTask ==  .invalid {
    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: UIApplication.didBecomeActiveNotification, object: nil)
}

This tells iOS to call your new method reinstateBackgroundTask() 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)
}

Note : While iOS no longer requires you to remove your observer, it’s still good practice to do so.

And there you have it; you can now do whatever you want, at least for as long as iOS says that it’s OK.

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 life. 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 users are looking at old data for at least several seconds until the new data comes in. Wouldn’t it be better if, when they opened your app, the new data were 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’s likely that a background fetch will occur 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.

Setting Up a Background Fetch

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’ll fetch the current time and won’t use the network.

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 URLSession ‘s background transfer service.

Implementation Details

Time to get started. First, open FetchViewController.swift and take a look at what this does.

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 you call when the process finishes. You’ll 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 is actually loaded. time is an optional type so if it isn’t set, it shows the message “Not updated yet.”

When the view first loads, you don’t fetch but go straight to updateUI() , which means the message “Not yet updated” appears first. Finally, when the users taps Update , it performs a fetch and, in its completion handler, updates the UI.

The view controller works; there’s nothing you need to do to it.

background-1-1-281x500.png

However, background fetching is not enabled.

Step One to Enabling Background Fetch

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.

U3YFruJ.png!web

Step Two to Enabling Background Fetch

Next, open AppDelegate.swift and add the following to application(_:didFinishLaunchingWithOptions:) :

UIApplication.shared.setMinimumBackgroundFetchInterval(
  UIApplication.backgroundFetchIntervalMinimum)

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.

Step Three to Enabling Background Fetch

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) {
  //1
  if let tabBarController = window?.rootViewController as? UITabBarController,
    let viewControllers = tabBarController.viewControllers {

    //2
    for viewController in viewControllers {
      if let fetchViewController = viewController as? FetchViewController {
        //3
        fetchViewController.fetch {
          //4
          fetchViewController.updateUI()
          completionHandler(.newData)
        }
      }
    }
  }
}

Here’s what’s going on above:

  1. Optionally cast the rootViewController as a UITabBarController since the rootViewController is not necessarily always as a UITabBarController in every app. However, the rootViewController is always a UITabBarController in this app, which means this piece of code will never fail.
  2. 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’s 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.
  3. Call fetch(_:) .
  4. When fetch(_:) completes, update the UI and then call the completionHandler that was passed in as a parameter. It’s 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 and .failed .

For simplicity, this 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 that it can present it in the card view of the app switcher. And that’s 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 be overwritten and never called. It’s better to pass the handler through and call it as it makes 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 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 .
    background-2-273x500.png
  • Xcode sends the app to the background. If the app stops in the debugger, instruct the debugger to continue.
    reAJfaF.png!web
  • Reopen the app.
  • Notice that the time is updated!

Testing When the App Is Resumed 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’s best to make a new Scheme with that option always set. Xcode makes this easy.

First, select the Manage Schemes option.

RRNn6ja.png!web

AJ3Azi6.png!web

Next, select the only scheme in the list, and then click on the Gear icon and select Duplicate .

ZvUZjuQ.png!web

Lastly, rename your scheme to something reasonable, like “Background Fetch,” and check the checkbox to Launch due to a background fetch event .

QBZzua.png!web

Run your app with this scheme. You’ll 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 mode effectively lets your users seamlessly get the latest content.

Where to Go From Here?

You can download the completed project files by clicking on the Download Materials button at the top or bottom of the tutorial.

If you want to read Apple’s documentation on what you 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 outURLSession 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!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK