How it works
The explanation, with examples you can run.
A session per kind of traffic
let api: URLSession = { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 15 config.waitsForConnectivity = true // wait for a network instead of failing at once config.httpAdditionalHeaders = ["Accept": "application/json"] return URLSession(configuration: config)}() func fetchOrders() async throws -> [Order] { let (data, response) = try await api.data(from: Order.listURL) guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { throw APIError.badStatus } return try JSONDecoder().decode([Order].self, from: data)}URLSession.shared is fine for simple calls but can't be configured. Create your own sessions for anything else and keep them for the life of the app: each session owns a connection pool, and making one per request throws away connection reuse. The three configurations are default (disk cache, cookies, credentials), ephemeral (nothing written to disk) and background (below).
Two things that catch people out. A 404 or 500 is not an error to URLSession: you get a response and must check the status code yourself. And cancelling the Swift task that is awaiting data(from:) cancels the request, which is what you want when a SwiftUI .task is torn down.
All of this goes over HTTPS by default: App Transport Security blocks plain HTTP and weak TLS unless you add exceptions in Info.plist.
Caching you get for free
Responses are stored in URLCache (memory and disk) and reused according to the server's Cache-Control, ETag and Last-Modified headers; that is what the default policy, useProtocolCachePolicy, means. You can override it per request, for example .returnCacheDataElseLoad to show something offline. Apple's docs point out that the default policy also writes HTTPS responses to disk, which you may not want for personal data; use an ephemeral session or veto caching in the session delegate.
Transfers that survive suspension
A normal session stops when your app is suspended. For uploads and downloads that must finish anyway, use a background session: the transfer is done by a system process, which can wait for Wi-Fi or power when you set isDiscretionary.
let config = URLSessionConfiguration.background(withIdentifier: "com.example.sync")config.sessionSendsLaunchEvents = truelet session = URLSession(configuration: config, delegate: syncDelegate, delegateQueue: nil)session.downloadTask(with: exportURL).resume()Background sessions only support upload and download tasks, report through a delegate rather than async returns, and wake the app when they finish: the app delegate receives application(_:handleEventsForBackgroundURLSession:completionHandler:), you recreate the session with the same identifier, handle the delegate events, then call the completion handler. If the user force-quits the app, its background transfers are cancelled.
For retries, remember the phone's network is the least reliable part of the system: retry idempotent requests with backoff and jitter, and send an idempotency key with the rest.
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.