How can an iOS app do work in the background?
TL;DR
iOS suspends your app soon after it goes to the background. To keep working, pick the right tool:
| Need | Tool |
|---|---|
| Finish a short job (save, upload) | beginBackgroundTask |
| Refresh content now and then | BGAppRefreshTask |
| Long maintenance work (cleanup, ML) | BGProcessingTask |
| Large downloads or uploads | Background URLSession |
| Continue a user-started job with progress (iOS 26) | BGContinuedProcessingTask |
| Wake the app from the server | Silent push notification |
| Audio, navigation, VoIP calls | Background 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.