Background Modes Tutorial: Getting Started

In this tutorial, you’ll create an app that uses audio playback, location updates, critical tasks, and background fetch to learn about the most common background modes. By Chuck Krutsinger .

5 (3) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Background Fetch

Background fetch was introduced in iOS 7. It lets your app appear up-to-date while minimizing the impact on battery life. Starting with iOS 13, Apple introduced a new background task scheduler API that offers significant improvements.

Suppose, for example, you were implementing a news feed in your app. Prior to background fetch, you would refresh the feed each time the app launches.

Sadly, users would see old headlines for several seconds while the refresh occurs. And you know some would try to tap a story only to see it vanish and be replaced by an unrelated story. Not a good look.

Wouldn’t it be better if the latest headlines were magically there when users open your app? This is the ability background fetch gives you.

When enabled, the system leverages usage patterns to determine when to fire off a background fetch. For example, if a user opens your news app at 9 a.m. most days, a background fetch will likely occur sometime before 9 a.m. The system decides the best time to issue a background fetch, and for this reason, it’s unsuitable for critical updates.

Understanding Background Fetch

Background fetch is controlled by BGTaskScheduler, a sophisticated system for balancing all the factors that affect user experience, such as performance, usage patterns, battery life and more.

Background fetch usually 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 order to implement background fetch, you’ll need to do these tasks — but don’t do them yet:

  • Check the box Background fetch in the Background Modes of your app’s Capabilities.
  • Add an identifier to Info.plist for your refresh task.
  • Invoke BGTaskScheduler.register(forTaskWithIdentifier:using:launchHandler:) in your app delegate to handle the background fetch.
  • Create a BGAppRefreshTaskRequest with an earliestBeginDate for when to execute.
  • Submit the request using BGTaskScheduler.submit(_:).

Similar to background completion tasks, you have a short but indefinite timeframe to do a background fetch. The consensus figure is a maximum of 30 seconds, but plan for less. If you need to download large resources as part of the fetch, use URLSession‘s background transfer service.

Implementing Background Fetch

Time to get started. First, the easy part: Check the Background fetch mode capability under Signing & Capabilities.

Check the background fetch mode

Next, open Info.plist and tap + to add a new identifier.

Scroll down and select Permitted background task scheduler identifiers. Expand the item, then tap + next to that new identifier to add an entry.

Type com.mycompany.myapp.task.refresh for the value of the identifier.

Note: In your real projects, you would use your company’s URL in reverse as the root of the identifier, adding your app name and descriptive elements such as task.refresh. You could define multiple kinds of refresh tasks, each with its own identifier.

Add permitted background task identifier

Next, you need an AppDelegate class because iOS expects you to register your fetch task during application(_:didFinishLaunchingWithOptions:).

In the App folder, add a new Swift file named AppDelegate.swift. Then replace the existing code with the following:

import UIKit
import BackgroundTasks

class AppDelegate: UIResponder, UIApplicationDelegate {
  static var dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .long
    return formatter
  }()

  var window: UIWindow?

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    return true
  }
}

This code defines a date formatter for the refresh timestamp. It also includes an empty method for application(_:didFinishLaunchingWithOptions:), which is where you’ll register the background fetch task.

Now add the following function to AppDelegate:

func refresh() {
  // to simulate a refresh, just update the last refresh date
  // to current date/time
  let formattedDate = Self.dateFormatter.string(from: Date())
  UserDefaults.standard.set(
    formattedDate,
    forKey: UserDefaultsKeys.lastRefreshDateKey)
  print("refresh occurred")
}

This function is the simulation of a refresh.

In an app you create, you might fetch data from the network. For this tutorial, you’ll save a formatted timestamp to UserDefaults to show the time of the refresh execution.

Still in AppDelegate.swift, add the following function to AppDelegate:

func scheduleAppRefresh() {
  let request = BGAppRefreshTaskRequest(
    identifier: AppConstants.backgroundTaskIdentifier)
  request.earliestBeginDate = Date(timeIntervalSinceNow: 1 * 60)
  do {
    try BGTaskScheduler.shared.submit(request)
    print("background refresh scheduled")
  } catch {
    print("Couldn't schedule app refresh \(error.localizedDescription)")
  }
}

Here you create a BGAppRefreshTaskRequest then assign an earliestBeginDate one minute from the present time. Then you submit the request using BGTaskScheduler.submit(_:).

Now, replace the body of application(_:didFinishLaunchingWithOptions:) with:

BGTaskScheduler.shared.register(
  forTaskWithIdentifier: AppConstants.backgroundTaskIdentifier,
  using: nil) { task in
    self.refresh() // 1
    task.setTaskCompleted(success: true) // 2
    self.scheduleAppRefresh() // 3
}

scheduleAppRefresh()
return true

When iOS finishes launching the app, this code registers the task with the task scheduler and schedules the first refresh. The task itself, when executed, will:

  1. Perform the refresh.
  2. Mark the task as successfully completed.
  3. Schedule the next refresh.

Now you need to connect your AppDelegate to AppMain. Open AppMain.swift. Add this line just before the body:

@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

That is all you need for iOS to invoke the AppDelegate.

Build and run the app on a physical device. Check the Xcode console for a message to confirm the background refresh is scheduled.

Testing Background Fetch

One way to test background fetch is to sit around and wait for the system to decide to do it. But you might be sitting for a long time waiting for that to happen.

iOS makes no guarantees about when your refresh will execute. The system uses a variety of factors to decide when to execute, such app usage patterns, battery charge and others. Fortunately, Xcode gives you a way to trigger a background fetch with a debugger command.

Open RefreshView.swift and set a breakpoint at print("moved to background").

Then send the app to the background and Xcode should break at your new breakpoint. At the lldb prompt, type (or, since it’s quite complicated, copy and paste!) the following command:

e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.mycompany.myapp.task.refresh"]

This will instruct the debugger to execute the background refresh immediately.

Resume execution of the app. The console should display refresh occurred and then background refresh scheduled. Each refresh schedules another for the future. You may also see debug messages from the background task scheduler indicating its activities.

Next, reopen the app. The Refresh tab will display the time and date when the refresh occurred.

If you leave the app on your device and check over the next few days, you’ll see the timestamp updates from time to time. iOS invokes the refresh according to its calculations of when the best time is to refresh.