How it works
The explanation, with examples you can run.
async and await
func loadProfile(id: User.ID) async throws -> Profile { let (data, _) = try await URLSession.shared.data(from: Profile.url(for: id)) return try JSONDecoder().decode(Profile.self, from: data)}Each await marks a point where the function may suspend. While it waits, its thread goes back to the pool to run other work; nothing is blocked. Code between two awaits runs straight through. The function may resume on a different thread than it started on, which is why isolation, not threads, is what you reason about.
Tasks: structured first
func loadDashboard() async throws -> Dashboard { async let profile = loadProfile(id: me) // child task 1 async let feed = loadFeed() // child task 2, runs in parallel return try await Dashboard(profile: profile, feed: feed)}async let and withTaskGroup create child tasks: they can't outlive the function, if the function exits with an error the children still running are cancelled, and cancelling the parent cancels them all. Cancellation is cooperative: a task is only marked, and your code (or the API it awaits) decides when to stop.
Task { ... } starts an unstructured task that inherits the current actor and priority; Task.detached inherits neither. In SwiftUI, prefer .task { } on a view, which is cancelled automatically when the view goes away.
Actors and the main actor
actor ImageCache { private var images: [URL: UIImage] = [:] func image(for url: URL) -> UIImage? { images[url] } func insert(_ image: UIImage, for url: URL) { images[url] = image }} let cached = await cache.image(for: url) // outside callers must awaitAn actor serialises access to its state, so callers from outside must await. Actors are re-entrant: while one of its methods is suspended at an await, the actor can run other calls, so don't assume state is unchanged after an await.
@MainActor is a global actor tied to the main thread. SwiftUI's View is declared @MainActor, so your views and their body are too, and marking a view model @MainActor makes the compiler check that UI state is only touched there.
Sendable and Swift 6
A value is Sendable when it is safe to share across isolation domains: value types made of Sendable parts, immutable final classes, actors, and classes that guard their own state. In the Swift 6 language mode (opt-in per target, introduced with Swift 6 in 2024), passing a non-Sendable value between actors is a compile error, where Swift 5 mode only warned, if you turned the checks on.
Swift 6.2 (2025) made this easier to adopt. A target can choose to be main-actor isolated by default, which suits app code that is mostly UI, and an upcoming-feature setting makes nonisolated async functions run on the caller's actor instead of always hopping to the thread pool. You then mark code that should run in parallel with @concurrent. Check your target's build settings: the same code can be isolated differently depending on these options.
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.