iosNudge

How can an iOS app do work in the background?

MediumAsked oftenApp lifecyclePerformance 2 min read

TL;DR

iOS suspends your app soon after it goes to the background. To keep working, pick the right tool:

NeedTool
Finish a short job (save, upload)beginBackgroundTask
Refresh content now and thenBGAppRefreshTask
Long maintenance work (cleanup, ML)BGProcessingTask
Large downloads or uploadsBackground URLSession
Continue a user-started job with progress (iOS 26)BGContinuedProcessingTask
Wake the app from the serverSilent push notification
Audio, navigation, VoIP callsBackground modes

The system decides when most tasks run. You cannot force exact times.

1. Finish a short task

func sceneDidEnterBackground(_ scene: UIScene) {
    var taskID: UIBackgroundTaskIdentifier = .invalid
    taskID = UIApplication.shared.beginBackgroundTask(withName: "SaveDraft") {
        // Time is up. Clean up fast.
        UIApplication.shared.endBackgroundTask(taskID)
    }
    Task {
        await draftStore.save()
        UIApplication.shared.endBackgroundTask(taskID)
    }
}

You get a short time, often around 30 seconds. Always call endBackgroundTask, or the system may kill the app.

2. BGTaskScheduler

Step 1: add the identifiers to Info.plist under BGTaskSchedulerPermittedIdentifiers, and turn on the "Background fetch" and/or "Background processing" modes.

Step 2: register before launch finishes.

import BackgroundTasks

func application(_ app: UIApplication,
                 didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.refresh", using: nil) { task in
        self.handleRefresh(task as! BGAppRefreshTask)
    }
    return true
}

Step 3: schedule and handle it.

func scheduleRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: "com.example.refresh")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
    try? BGTaskScheduler.shared.submit(request)
}

func handleRefresh(_ task: BGAppRefreshTask) {
    scheduleRefresh() // schedule the next one
    let work = Task {
        let ok = await feedStore.refresh()
        task.setTaskCompleted(success: ok)
    }
    task.expirationHandler = { work.cancel() }
}
  • BGAppRefreshTask: about 30 seconds. Runs based on how often the user opens the app.
  • BGProcessingTask: minutes. Often at night while charging. Can require network or power.

SwiftUI has a modifier: .backgroundTask(.appRefresh("com.example.refresh")) { await refresh() }.

3. Background URLSession

let config = URLSessionConfiguration.background(withIdentifier: "com.example.downloads")
config.isDiscretionary = true  // let the system pick a good time (Wi-Fi, charging)
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
session.downloadTask(with: videoURL).resume()

The transfer runs in a system process. It continues if your app is suspended or even terminated by the system. When it finishes, iOS relaunches your app and calls application(_:handleEventsForBackgroundURLSession:completionHandler:). Use delegates, not async methods, with background sessions.

4. BGContinuedProcessingTask (iOS 26)

For work the user started, like exporting a video. The system shows a progress UI, and the task keeps running after the user leaves the app. You must report progress, or the system may stop it.

5. Silent push

A push with "content-available": 1 and no alert wakes the app for about 30 seconds. The system limits how often. Do not rely on it for every message.

6. Background modes

Turned on in Signing & Capabilities. Each needs a real reason, or App Review rejects it:

  • Audio: music and podcast playback.
  • Location updates: navigation, fitness tracking.
  • Voice over IP: with CallKit and PushKit.
  • External accessory / Bluetooth: talk to devices.

Testing tip

In the debugger, you can trigger a scheduled task:

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

Common follow-up questions

  • Why does my background task never run? Not registered before launch ended, missing Info.plist identifier, Low Power Mode, or the user rarely opens the app.
  • What if the user force-quits the app? Background refresh and silent pushes stop until the user opens the app again.
  • Can a timer run in the background? No. Timers stop when the app is suspended.
  • How do you save battery? Use isDiscretionary, batch work, and require power for heavy processing tasks.