How it works
The explanation, with examples you can run.
The menu
- Finish what you started:
beginBackgroundTask(withName:expirationHandler:)buys a short extension to complete work in progress, such as saving a file or sending a message. Call it before leaving the foreground and balance it withendBackgroundTask(_:). - Refresh content:
BGAppRefreshTaskruns briefly (Apple documents up to 30 seconds) at a time iOS predicts is useful, often shortly before the person usually opens your app. - Heavy maintenance:
BGProcessingTaskcan run for minutes, but only while the device is idle, and can require power or network. Good for database clean-up, ML training, large syncs. - Long jobs the user started:
BGContinuedProcessingTask(new in iOS 26) starts from a user action, such as an export, and may continue after the app is backgrounded, with progress shown by the system so the person can cancel it. - Transfers: background
URLSessionuploads and downloads (see networking). - Woken by your server: background push notifications (see push).
- Declared modes: audio playback, navigation, VoIP and Bluetooth accessories keep running under their own
UIBackgroundModes, and are reviewed for real use.
Scheduling a refresh
Each task identifier must be listed under BGTaskSchedulerPermittedIdentifiers in Info.plist, with the matching background mode (fetch for refresh, processing for processing). In SwiftUI the handler is a scene modifier:
@mainstruct NewsApp: App { var body: some Scene { WindowGroup { ContentView() } .backgroundTask(.appRefresh("com.example.news.refresh")) { scheduleRefresh() // one-off: book the next one await Headlines.shared.refresh() // cancelled if time runs out } }} func scheduleRefresh() { let request = BGAppRefreshTaskRequest(identifier: "com.example.news.refresh") request.earliestBeginDate = .now.addingTimeInterval(30 * 60) // not before; maybe much later try? BGTaskScheduler.shared.submit(request)}In UIKit you register a launch handler with BGTaskScheduler.shared.register(forTaskWithIdentifier:using:launchHandler:) before launch finishes, set the task's expirationHandler, and call setTaskCompleted(success:) when done. In SwiftUI the task completes when your closure returns, and running out of time cancels it.
Why it is this strict, and what to do about it
Background time is a budget the system spreads across all apps. Apps the person rarely opens get less; apps that finish quickly and report completion are treated better; a force-quit app gets nothing until it is opened again. Treat every background grant as a bonus:
- Make work resumable and idempotent, so being cut off halfway is harmless.
- Do the essential part first (save, then upload; fetch headlines, then images).
- When the server knows something changed, send a push instead of polling.
- Test with Xcode's debugger commands for simulating task launch; on a real device, background tasks almost never run on the schedule you expect.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.