Background Tasks and Networking on watchOS
Background refresh and URLSession on the watch, the rule in Apple's technote TN3135 that blocks low-level networking for most apps, the move to BGTaskScheduler in watchOS 27, and the task-completion contract that prevents mystery SIGKILL crashes.
When to Use
Use this skill when you're:
- Picking between SwiftUI
.backgroundTask(_:action:)and a WatchKit-stylehandle(_:)implementation - Scheduling app refresh, with
BGTaskScheduleron the 27 SDK or the legacyscheduleBackgroundRefresh(withPreferredDate:userInfo:scheduledCompletion:) - Migrating WatchKit background scheduling to
BGTaskScheduler(the WatchKit methods are deprecated in watchOS 27) - Choosing between default, ephemeral, and background
URLSessionconfigurations on a Watch target - Hitting
ENETDOWNwhen startingNWConnectionon watchOS and wondering why it "works in the simulator" - Handling
WKURLSessionRefreshBackgroundTaskorWKWatchConnectivityRefreshBackgroundTaskwake-ups - Debugging
EXC_CRASH (SIGKILL)crashes shortly after a background wake
Example Prompts
Questions you can ask your assistant that will draw from this skill:
- "Why does NWConnection return ENETDOWN on my Apple Watch but work in the simulator?"
- "My watch app crashes with SIGKILL a minute after waking in the background. What's the cause?"
- "Should I use .backgroundTask or implement handle(_😃 for my refresh?"
- "Which URLSession configuration should I use for a download that might outlive the app?"
- "My scheduled background refresh never fires. What am I missing?"
- "How do I move my watch app's background refresh to BGTaskScheduler?"
What This Skill Provides
URLSession Over Everything
- The TN3135 policy:
NWConnection,NWPathMonitor,URLSessionStreamTask,URLSessionWebSocketTask,NWBrowser,NetService, and BSD sockets are blocked for normal apps - The three narrow exceptions (background audio, VoIP with CallKit, tvOS pairing) and their minimum versions
- Why the simulator doesn't enforce the rule, which masks failures you'll only see on a device
URLSession Configuration Matrix
- Default, ephemeral, and background configurations, and when each fits
- Why
URLSession.sharedis wrong for a transfer that might outlive the app - Avoiding
Data(contentsOf:)and other synchronous loaders, which watchOS doesn't support for network URLs
BGTaskScheduler in watchOS 27
BGAppRefreshTaskRequestwith anearliestBeginDate, submitted withsubmitTaskRequest- One identifier and one
.backgroundTask(.appRefresh("identifier"))hook per refresh flow, each listed inBGTaskSchedulerPermittedIdentifiers - What's deprecated (WatchKit scheduling,
userInfodispatch, the bare.appRefresh) and what's gone (manually scheduled snapshots) - Processing, health research, and continued-processing task requests on the watch, and why building requires the Xcode 27 SDK even when you deploy back to watchOS 26
SwiftUI .backgroundTask Before watchOS 27
- The bare
.appRefreshhook, whose closure receives the scheduleduserInfostring directly - Wrapping long work in
withTaskCancellationHandlerso it cancels cleanly before the budget runs out
The Delegate Path with handle(_:)
- Branching on
WKApplicationRefreshBackgroundTask,WKSnapshotRefreshBackgroundTask,WKURLSessionRefreshBackgroundTask, andWKWatchConnectivityRefreshBackgroundTask - Completing every task, the contract that prevents
EXC_CRASH (SIGKILL) - Using
expirationHandlerto move in-flight work into a backgroundURLSessionbefore suspension
Background URLSession Wake-Up Flow
- The full sequence: the system wakes the app,
handle(_:)receives the task, the session delegate callbacks fire, and then the task completes - Moving the downloaded file out of its temporary location inside the delegate before returning
Budget, Freshness, and Debugging
- What raises background priority (a complication on the active face, the app in the Dock) and what throttles it (workouts, navigation, low battery)
- Designing for missed refreshes, and always refreshing when the app comes to the foreground
- A fresh-data strategy table, from
URLSessionon the watch to widget push updates - Testing over all three network routes the watch uses: the iPhone over Bluetooth, known Wi-Fi, and cellular
- A debugging checklist and a common-mistakes table
Key Pattern
Every background task branch must reach setTaskCompletedWithSnapshot(_:), and URLSession and Watch Connectivity tasks must complete after the session delegate finishes, not inside handle(_:):
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
switch task {
case let t as WKURLSessionRefreshBackgroundTask:
savedURLSessionTasks.append(t) // complete after session delegate fires
case let t as WKWatchConnectivityRefreshBackgroundTask:
savedWCTasks.append(t) // complete after WCSession settles
default:
task.setTaskCompletedWithSnapshot(false)
}
}
}A branch that never completes its task drains the background-time budget, and the app eventually crashes with EXC_CRASH (SIGKILL).
Documentation Scope
This page documents the background-and-networking skill (in the axiom-watchos suite), which your assistant draws on when you ask about .backgroundTask, BGTaskScheduler, background URLSession, TN3135 networking limits, or the task-completion contract.
- For general
URLSessionpatterns beyond the watch's constraints, see networking
Related
- platform-basics – The SwiftUI
.backgroundTaskhook in theAppbody, and when to adopt a delegate - watch-connectivity – When the background wake is a Watch Connectivity delivery rather than a finished
URLSessiontransfer - smart-stack-and-complications – Widget push updates, which replace some background-refresh flows
- background-processing – iOS-side
BGTaskSchedulerpatterns; this skill covers what's specific to watchOS - swift-concurrency –
withTaskCancellationHandlerand cancellation semantics inside.backgroundTaskclosures
Resources
WWDC: 2019-716
Docs: /technotes/tn3135-low-level-networking-on-watchos, /watchkit/using-background-tasks, /watchos-apps/making-background-requests, /watchos-apps/keeping-your-watchos-app-s-content-up-to-date, /swiftui/scene/backgroundtask(_:action:), /backgroundtasks/bgtaskscheduler, /backgroundtasks/bgapprefreshtaskrequest, /watchkit/wkapplication/schedulebackgroundrefresh(withpreferreddate:userinfo:scheduledcompletion:), /watchkit/wkrefreshbackgroundtask, /watchkit/wkurlsessionrefreshbackgroundtask, /foundation/urlsession, /foundation/urlsessionconfiguration/background(withidentifier:)