100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Swift

Concurrency in Swift (async/await)

Explore Swift's modern async/await concurrency model, Task, and actors for writing safe asynchronous code.

Error Handling & ConcurrencyAdvanced11 min readJul 8, 2026
Analogies

Introduction

Starting with Swift 5.5, the language introduced a native concurrency model built around async and await. This replaces much of the older completion-handler and Grand Central Dispatch (GCD) style code with straight-line, readable syntax for asynchronous work, while still running efficiently on a cooperative thread pool. Alongside async/await, Swift added Task for launching units of asynchronous work and actors for protecting mutable state from data races in concurrent code.

🏏

Cricket analogy: Swift 5.5's async/await is like replacing a chain of walkie-talkie callbacks between the umpire, scorer, and broadcast van with a single straight-line script that just 'awaits' each confirmation in order; a Task {} launches a new replay-review process, and an actor is like the official scorer who's the only one allowed to touch the scorebook at a time, preventing two officials from editing the same over simultaneously.

Syntax

swift
func fetchData() async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

Task {
    do {
        let data = try await fetchData()
        print("Fetched \(data.count) bytes")
    } catch {
        print("Fetch failed: \(error)")
    }
}

Explanation

A function marked async can suspend execution at an await point without blocking the underlying thread, allowing other work to run while it waits. Calling an async function requires the await keyword, and if the function is also throws, the call is written try await. Task { ... } creates a new asynchronous context, often used to bridge synchronous code (like a button action) into the async world. Because async functions can suspend and resume on different threads, shared mutable state can still race; actors solve this by serializing all access to their mutable properties, so only one task can touch that state at a time.

🏏

Cricket analogy: Fetching live ball-by-ball data with await lets the app suspend at that point without freezing the whole broadcast thread, and if the fetch can fail it's written try await; tapping a 'refresh score' button triggers a Task { } to bridge that synchronous tap into the async fetch, and the shared scorecard actor serializes updates so two feeds can't edit the same over at once.

Example

swift
actor DataStore {
    private var cache: [String: Int] = [:]

    func set(_ key: String, _ value: Int) {
        cache[key] = value
    }

    func get(_ key: String) -> Int? {
        cache[key]
    }
}

func fetchScore() async -> Int {
    try? await Task.sleep(nanoseconds: 200_000_000)
    return 42
}

let store = DataStore()

Task {
    let score = await fetchScore()
    await store.set("level1", score)
    let saved = await store.get("level1")
    print("Saved score: \(saved ?? -1)")
}

Output

swift
Saved score: 42

Key Takeaways

  • Swift's async/await was introduced in Swift 5.5 and lets asynchronous code read like straight-line synchronous code.
  • Functions that suspend are marked async and are called with await; combined with throws, calls use try await.
  • Task { ... } launches a unit of asynchronous work, often bridging synchronous contexts into async code.
  • async/await largely replaces older completion-handler and GCD-based patterns for many common asynchronous tasks.
  • actors serialize access to their mutable state, preventing data races without manual locking.
  • Accessing an actor's members from outside requires await, since the actor may need to queue the request.

Practice what you learned

Was this page helpful?

Topics covered

#Swift#SwiftProgrammingStudyNotes#Programming#ConcurrencyInSwiftAsyncAwait#Concurrency#Async#Await#Syntax#StudyNotes#SkillVeris