trycatchthrow.io
Mobile

Swift Concurrency Made Practical

A practical guide to async/await, actors, and structured concurrency in Swift.

V

Vengatesan Ganesan

May 12, 20247 min read

Swift's concurrency model replaces callback pyramids with readable, structured code. Here's the practical subset you'll reach for every day.

async / await

Mark a function async, then await its result. The compiler handles the suspension points.

ProfileLoader.swift
func loadProfile(id: String) async throws -> Profile {
    let (data, _) = try await URLSession.shared.data(from: profileURL(id))
    return try JSONDecoder().decode(Profile.self, from: data)
}

Structured Concurrency

Run child tasks in parallel and gather results with async let or a task group — cancellation propagates automatically.

Dashboard.swift
async let profile = loadProfile(id: userID)
async let feed = loadFeed(for: userID)
let dashboard = try await Dashboard(profile: profile, feed: feed)

Actors Keep State Safe

An actor serializes access to its mutable state, eliminating data races by construction.

ImageCache.swift
actor ImageCache {
    private var store: [URL: Image] = [:]
    func image(for url: URL) -> Image? { store[url] }
    func insert(_ image: Image, for url: URL) { store[url] = image }
}

Watch out

Hopping onto an actor has a cost. Don't wrap tiny, hot values in an actor when a simpler synchronization primitive will do.

Key Takeaways

Reach for async/await for clarity, structured concurrency for parallelism, and actors for shared mutable state. Together they make correct concurrent code the default.