Swift Tutorial · Swift Concurrency · Lesson 24 of 30

Swift Actors, Sendable and Data-Race Safety

Protect mutable state with actors, understand Sendable, and how the Swift 6 language mode prevents data races.

Create a free account to save this page, take notes and track your progress. Sign in

Actors

An actor is like a class whose mutable state can only be touched by one task at a time. Calls from outside are awaited because they may have to wait their turn. Below, 1,000 concurrent increments always add up.

An actor protects its state
actor BankAccount {
    private(set) var balance = 0
    func deposit(_ amount: Int) { balance += amount }
}

let account = BankAccount()
await withTaskGroup(of: Void.self) { group in
    for _ in 1...1_000 {
        group.addTask { await account.deposit(1) }
    }
}
print(await account.balance)

Output

1000

Run this on your computer — see “Get Started” for setup.

Sendable and the main actor

In the Swift 6 language mode, the compiler checks that values crossing between tasks are Sendable (safe to share): value types made of Sendable parts, actors, and immutable final classes qualify. Sharing a mutable class between tasks is a compile-time error, not a crash in production. UI code runs on the @MainActor, and Swift 6.2 lets an app module make the main actor the default.

final class Counter { var value = 0 }      // not Sendable
let counter = Counter()
Task.detached { counter.value += 1 }        // error in Swift 6: capture of non-Sendable 'counter'
Sendable values and the main actor
struct Order: Sendable {                 // a value type of Sendable parts
    let id: Int
    let total: Double
}

actor OrderStore {
    private var orders: [Order] = []
    func add(_ order: Order) { orders.append(order) }
    func revenue() -> Double { orders.reduce(0) { $0 + $1.total } }
}

@MainActor
func showRevenue(_ value: Double) {
    print("UI: revenue is \(value)")
}

let store = OrderStore()
await withTaskGroup(of: Void.self) { group in
    for i in 1...4 {
        group.addTask { await store.add(Order(id: i, total: Double(i) * 10)) }
    }
}
await showRevenue(store.revenue())

Output

UI: revenue is 100.0

Run this on your computer — see “Get Started” for setup.

Test yourself

What happens in the Swift 6 language mode if you share a non-Sendable mutable class between tasks?

  1. It crashes at runtime
  2. The compiler reports an error
  3. Swift copies the object
  4. Nothing
Show answer

Answer: The compiler reports an error

Was this page helpful?