name: background-execution description: Writes and reviews Swift code for background execution on Apple platforms - BGTaskScheduler (app refresh, processing, continued processing), beginBackgroundTask assertions, background URLSession downloads/uploads, silent and VoIP push, background modes (audio, location, BLE), the SwiftUI .backgroundTask modifier, and macOS schedulers. Use when scheduling deferred work, finishing work after the app backgrounds, transferring files in the background, waking the app via push, or debugging background tasks that never run. license: MIT metadata: author: Anton Novoselov version: "1.0"
Write and review Swift code that runs work while the app is backgrounded or suspended, choosing the right API for the job and respecting the system's constraints so the work actually runs and the app is not terminated.
Review process:
- Establish the background execution model (lifecycle, budgets, force-quit, the five principles) using
references/fundamentals.md. - Confirm the chosen API matches the work using the decision tree below; reroute if it does not.
- Validate
BGTaskSchedulerusage (registration timing, Info.plist, expiration,setTaskCompleted) usingreferences/bg-task-scheduler.md. - Validate task assertions (
beginBackgroundTask/endBackgroundTask,performExpiringActivity) usingreferences/task-assertions.md. - Validate background
URLSessionconfig and the relaunch flow usingreferences/background-url-session.md. - Validate silent/VoIP push payloads, headers, and handlers using
references/background-push.md. - Validate
UIBackgroundModesdeclarations and location specifics usingreferences/background-modes.md. - Validate background audio (session category, recording while locked, interruptions, route changes, mic permission) using
references/background-audio.md. - Validate SwiftUI
ScenePhase/.backgroundTaskusage usingreferences/swiftui-background-tasks.md. - Validate macOS schedulers (
NSBackgroundActivityScheduler,beginActivity,SMAppService) usingreferences/macos-background.md. - Check testing/debugging and constraint handling using
references/testing-and-debugging.md. - Catch common mistakes using
references/anti-patterns.md.
If doing partial work, load only the relevant reference files.
Task-based routing
Match the user's goal to the read order. Load only what you need.
"Refresh my app's content in the background"
references/bg-task-scheduler.md-BGAppRefreshTask, register/submit/expirationreferences/testing-and-debugging.md- simulate the launch, handlebackgroundRefreshStatus
"Run long maintenance / ML / cleanup in the background"
references/bg-task-scheduler.md-BGProcessingTask, power/network requirementsreferences/testing-and-debugging.md- simulate launch and expiration
"Let a user-started export keep running after they leave (iOS 26)"
references/bg-task-scheduler.md-BGContinuedProcessingTask, progress, submission strategy, GPUreferences/fundamentals.md- the consent / user-initiated principle
"Finish an in-flight upload / save when the app backgrounds"
references/task-assertions.md-beginBackgroundTask/endBackgroundTask,performExpiringActivity
"Download or upload a large file that survives suspension"
references/background-url-session.md- background config, delegate, the relaunch flowreferences/testing-and-debugging.md- force-quit and constraint caveats
"Wake my app from the server"
references/background-push.md- silent push payload/headers/handler, or PushKit for callsreferences/background-modes.md- theremote-notificationmode
"Keep playing audio in the background"
references/background-audio.md- theaudiomode + active session, category/mode/options, interruptions, route changes
"Record / dictate / transcribe with the screen locked"
references/background-audio.md-.playAndRecord, recording while locked, mic permission, AVAudioEngine taps, the production gotchas
"Track location in the background"
references/background-modes.md- thelocationmode, significant-change and region monitoring
"Do this the SwiftUI way"
references/swiftui-background-tasks.md-ScenePhase,.backgroundTask(.appRefresh/.urlSession)references/bg-task-scheduler.md- you still schedule withBGTaskScheduler
"Background work on macOS"
references/macos-background.md-NSBackgroundActivityScheduler,beginActivity,SMAppService
"My background task never runs / how do I test it"
references/testing-and-debugging.md- lldb simulate commands, the states that gate executionreferences/anti-patterns.md- the usual causes
"I'm hitting bugs or App Review issues"
references/anti-patterns.md- catches with before/after fixes
Decision tree
Which background API should I use?
Does the work require user consent or input mid-flight?
-> Background runtime is wrong. Bring it to the foreground.
Is work already running and you just need to finish it as the app backgrounds?
-> beginBackgroundTask / endBackgroundTask (apps)
-> ProcessInfo.performExpiringActivity (app extensions, watchOS)
Is it a user-initiated long task that should continue with visible progress? (iOS/iPadOS 26)
-> BGContinuedProcessingTask
Is it a large/long file download or upload that must survive suspension/termination?
-> background URLSession
Does the server need to tell the app new content exists?
-> background (silent) push (apns-push-type: background, content-available: 1)
Is it an incoming call invitation?
-> PushKit + CallKit (report the call on every push)
Is it periodic content refresh before the user opens the app?
-> BGAppRefreshTask
Is it long, deferrable maintenance / ML / cleanup?
-> BGProcessingTask (optionally requiresExternalPower / requiresNetworkConnectivity)
Does the app need to keep playing audio or recording the mic (incl. while locked)?
-> audio UIBackgroundMode + active AVAudioSession (.playback or .playAndRecord) [background-audio.md]
Does the app need to keep running for location / BLE / VoIP?
-> declare the matching UIBackgroundModes capability [background-modes.md]
macOS, deferrable repeating maintenance?
-> NSBackgroundActivityScheduler
macOS, keep critical non-UI work from being napped?
-> ProcessInfo.beginActivity / performActivity
Which BGTask type?
Short content refresh, system picks the time around app usage?
-> BGAppRefreshTask (UIBackgroundModes: fetch)
Long maintenance, ideally on charger + network?
-> BGProcessingTask (UIBackgroundModes: processing; requiresExternalPower / requiresNetworkConnectivity)
User-tapped long work with progress UI, continues after backgrounding? (iOS 26)
-> BGContinuedProcessingTask (title/subtitle, progress, submission strategy, optional .gpu)
Long processing of health-research-study data? (iOS 17)
-> BGHealthResearchTask
Core Instructions
- Never treat background execution as guaranteed. It is opportunistic and discretionary; design every feature with a foreground reconciliation path and never put correctness-critical logic solely in a background task.
- Never register a
BGTaskSchedulerhandler lazily, in view code, or after launch finishes. Register all handlers inApp.init()/application(_:didFinishLaunchingWithOptions:), because the system may launch the app directly into the background. (BGContinuedProcessingTaskis the one exception - it registers dynamically on user intent.) - Never register the same task identifier twice - it crashes the app.
- Never omit the expiration handler on a
BGTask; without one the system silently marks the task complete-and-unsuccessful. Set it, have it cancel work fast, and callsetTaskCompleted(success:)exactly once. - Never forget to reschedule.
BGTaskSchedulerrequests are one-shot; submit the next request as the first line of the launch handler. - Put
requiresExternalPower/requiresNetworkConnectivityonly onBGProcessingTaskRequest, never on app-refresh. TreatearliestBeginDateas a floor, not a schedule. - Never leave a
beginBackgroundTaskunbalanced. CallendBackgroundTaskon both the success path and inside the expiration handler, idempotently (guard on.invalid); an unbalanced assertion kills the app. CallbeginBackgroundTaskbefore starting the work, not at the end of the background-transition delegate. - Never trust
backgroundTimeRemainingas a real budget - it isDBL_MAXin the foreground and advisory in the background. Plan for ~30 s and rely on the expiration handler. - Use
beginBackgroundTask(apps) orperformExpiringActivity(extensions, watchOS) only to finish work already running, never to start fresh long work or schedule future work. - Never use completion-handler convenience methods or
dataTaskon a backgroundURLSession; background sessions require a delegate. Create one session per identifier, store it, and recreate it at relaunch with the identical identifier and config. - Implement the full relaunch flow for background
URLSession: store the handler fromhandleEventsForBackgroundURLSession, recreate the session, and call the stored handler fromurlSessionDidFinishEvents(forBackgroundURLSession:)on the main thread. Move the file fromdidFinishDownloadingTo'slocationsynchronously inside the callback. - For background uploads, use file-backed
uploadTask(with:fromFile:)- data and stream uploads fail after the app exits. - Never send a silent push without
apns-push-type: background(andapns-priority: 5), and keep theapsdictionary tocontent-availableonly. Call the fetch completion handler within ~30 s. Treat silent push as throttled and coalesced, not reliable. - On iOS 13+, report an incoming call to CallKit on every VoIP push, or the system terminates the app and revokes VoIP-push privileges. Never use VoIP pushes for non-call data.
- Never set
allowsBackgroundLocationUpdates = truewithout thelocationbackground mode - it is a fatal error. Prefer significant-change / region monitoring over the continuouslocationmode. - Background audio needs both the
audiomode and an activeAVAudioSessionwith a background-capable category (.playbackfor playback,.playAndRecord/.recordfor recording-while-locked); the mode alone does nothing. Activate the session when audio begins (not at launch) and deactivate with.notifyOthersOnDeactivation. Always handleinterruptionNotification(check.shouldResumebefore resuming) androuteChangeNotification(pause on.oldDeviceUnavailable). - For mic capture, require permission with
AVAudioApplication.requestRecordPermission()(iOS 17+) and declareNSMicrophoneUsageDescription- without the key the app crashes, and without permission capture returns silent zeroed samples, not an error. InstallAVAudioEnginetaps off the main actor (the closure runs on a realtime thread) and matchAVAudioFilesettings to the input node's format. - Declare
UIBackgroundModessparingly; App Review rejects unjustified modes. Prefer the lighter alternative. - Account for the user: a force-quit suppresses
BGTaskScheduler, silent push, and backgroundURLSessionrelaunch until the next manual launch. ReadbackgroundRefreshStatus(degrade on.denied, stay silent on.restricted) and back off under Low Power Mode and elevated thermal state. - Do background-relevant setup (task registration, session recreation, dependency wiring) on a path that runs on every launch, not in
.onAppear/.task- a background launch may never build the UI. - With scenes enabled, use
sceneDidEnterBackground(_:)(not the deprecatedapplicationDidEnterBackground(_:)); it has ~5 s to return. - On macOS use
NSBackgroundActivitySchedulerfor deferrable maintenance andProcessInfo.beginActivity/endActivity(balanced, orperformActivity) to control App Nap;performExpiringActivitydoes not exist on macOS. - Test background tasks on a real device with the lldb
_simulateLaunchForTaskWithIdentifier:/_simulateExpirationForTaskWithIdentifier:commands - never ship a reference to them. Always test the expiration path.
Output Format
If the user asks for a review, organize findings by file. For each issue:
- State the file and relevant line(s).
- Name the anti-pattern being replaced.
- Show a brief before/after code fix.
Skip files with no issues. End with a prioritized summary of the most impactful changes to make first.
If the user asks you to write or fix background-execution code, make the changes directly instead of returning a findings report.
Example output:
SyncManager.swift
Line 22: BGTaskScheduler handler registered in .onAppear - never runs on a background launch.
// Before
.onAppear {
BGTaskScheduler.shared.register(forTaskWithIdentifier: id, using: nil) { ... }
}
// After - register in App.init() / didFinishLaunching
init() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: id, using: nil) { task in
self.handleRefresh(task: task as! BGAppRefreshTask)
}
}
Line 48: missing expiration handler and setTaskCompleted - the app is killed at expiry.
// Before
func handleRefresh(task: BGAppRefreshTask) { refresh() }
// After
func handleRefresh(task: BGAppRefreshTask) {
scheduleNext()
let op = RefreshOperation()
task.expirationHandler = { op.cancel() }
op.completionBlock = { task.setTaskCompleted(success: !op.isCancelled) }
queue.addOperation(op)
}
Summary
- Registration timing (high): move the handler to
App.init()or it never fires on background launch. - Completion contract (high): add the expiration handler and call
setTaskCompletedexactly once.
End of example.
References
references/fundamentals.md- the execution model: foreground/background/suspended, system budgets, force-quit, user toggles, the five principles, the lifecycle and scene states, the "which API" map.references/bg-task-scheduler.md-BackgroundTasks:BGAppRefreshTask,BGProcessingTask,BGContinuedProcessingTask(iOS 26),BGHealthResearchTask, registration, Info.plist keys, submit, expiration, errors.references/task-assertions.md-beginBackgroundTask/endBackgroundTask,backgroundTimeRemaining,ProcessInfo.performExpiringActivity, assertions vsBGTaskScheduler.references/background-url-session.md- backgroundURLSessionconfig, mandatory delegate, the relaunch /handleEventsForBackgroundURLSessionflow, knobs, resume data.references/background-push.md- silent/background push (payload, headers, handler, throttling), PushKit/VoIP + the CallKit requirement.references/background-modes.md- theUIBackgroundModesmatrix, background location, BLE/accessory/PTT, deprecated background fetch.references/background-audio.md- background playback and recording:AVAudioSessioncategories/modes/options, recording while locked, mic permission, interruptions, route changes,AVAudioEnginetaps and production gotchas.references/swiftui-background-tasks.md-ScenePhase, the.backgroundTaskscene modifier, async cancellation, scheduling vs handling.references/macos-background.md-NSBackgroundActivityScheduler,ProcessInfo.beginActivity/App Nap,SMAppService, cross-platform map.references/testing-and-debugging.md- lldb simulate-launch/expiration,backgroundRefreshStatus, Low Power Mode, thermal state, what gates execution.references/anti-patterns.md- common mistakes LLMs make when generating background-execution code.