Swift Concurrency
Swift's async/await and Task API let you write asynchronous code that reads like ordinary sequential code while the runtime handles suspension and scheduling.
Why Structured Concurrency?
Before async/await, asynchronous Swift code relied on nested completion handlers that were hard to read and easy to get wrong. Marking a function async lets it suspend at await points without blocking the thread, and the compiler enforces that every suspension point is visible at the call site.
An Async Function
struct NetworkError: Error {}
func fetchUserName(id: Int) async throws -> String {
// Simulates a network call
try await Task.sleep(nanoseconds: 500_000_000)
guard id > 0 else { throw NetworkError() }
return "user_\(id)"
}
func loadProfile(id: Int) async throws -> String {
let name = try await fetchUserName(id: id)
return "Profile for \(name)"
}Launching Work with Task
await can only be used inside an async context. Task { } bridges from ordinary synchronous code into the async world, starting new unstructured work that begins running immediately without blocking the line after it.
Starting Async Work From Sync Code
func startLoading() {
Task {
do {
let profile = try await loadProfile(id: 7)
print(profile) // Profile for user_7
} catch {
print("Failed to load: \(error)")
}
}
print("Task launched, continues without waiting")
}
startLoading()Running Work in Parallel with async let
A plain await runs one call at a time. async let starts several async calls concurrently and lets execution continue until you actually need their results, at which point await collects them together.
Concurrent Calls With async let
func loadDashboard() async throws {
async let userName = fetchUserName(id: 1)
async let userName2 = fetchUserName(id: 2)
// Both calls run concurrently; execution pauses here
// until both finish.
let results = try await [userName, userName2]
print(results) // ["user_1", "user_2"]
}
Task {
try await loadDashboard()
}Task Cancellation and Priority
Tasks can be cancelled cooperatively — cancellation doesn't stop execution automatically, but well-behaved async code checks for it and exits early. Tasks can also be given a priority hint, such as .background or .userInitiated, to influence scheduling.
- await can only be called from an async function or inside a Task
- async functions can also throw, so pair them with try when they do
- await suspends the current task without blocking the underlying thread
- Structured concurrency (Task, async let, TaskGroup) ensures child work finishes or is cancelled before its parent scope exits
Exercise: Swift Concurrency
What does marking a function async indicate about it?