Swift Concurrency

Swift Concurrency

Modern applications perform multiple complex tasks simultaneously to keep the user interface fast and responsive.

Downloading images, saving files to a database, and fetching network data can take time.

If you run these tasks sequentially on the main thread, your app will freeze until the tasks finish.

Swift provides powerful, built-in concurrency tools—most notably async and await—to write asynchronous code safely and cleanly.


Asynchronous Functions (async)

An asynchronous function is a special type of function that can suspend its execution partway through.

While it is suspended, it gives up its thread, allowing the system to use that thread to do other work.

You mark a function as asynchronous by writing the async keyword after its parameters.

Defining an Async Function:

// The async keyword indicates this might take some time
func fetchUserData() async -> String {
    // Imagine a network call happens here
    return "User: Akash"
}

Awaiting Results (await)

Because an asynchronous function can suspend execution, you cannot call it like a normal function.

You must "await" its result. By placing the await keyword in front of the call, you acknowledge that the code might pause at this exact line.

Calling Async Functions:

func fetchUserData() async -> String {
    return "User: Akash"
}

func displayUser() async { print("Fetching data...") // Execution pauses here until fetchUserData() finishes let data = await fetchUserData() print("Received: \(data)") }

The code reads linearly from top to bottom, making it dramatically easier to understand than older callback-based approaches.


Concurrent Execution with Tasks

If you want to call an asynchronous function from a synchronous context (like a regular button click), you must bridge the gap.

You do this by creating a Task.

A Task provides a new concurrent context in which asynchronous code can run independently.

Creating a Task:

func fetchUserData() async -> String {
    return "User: Akash"
}

func displayUser() async { print("Fetching data...") let data = await fetchUserData() print("Received: \(data)") }

// Imagine this is a regular, synchronous function func buttonClicked() { Task { // We are now in a concurrent context! await displayUser() } print("Button click handled immediately.") }


Actors for Data Safety

When multiple threads try to read and write the exact same data at the exact same time, you get a "data race," which causes unpredictable crashes.

Swift solves this with Actors.

An actor is like a class, but it automatically ensures that only one task can access its mutable state at a time.

Defining an Actor:

actor BankAccount {
    var balance: Double = 0.0
    func deposit(amount: Double) {
        balance += amount
    }
}

If you want to read or write an actor's properties from the outside, you must use await to safely wait your turn!


Exercise

Which keyword do you place before an asynchronous function call to mark a potential suspension point?