Declared with actor instead of class, an actor's methods and properties are automatically actor-isolated — code OUTSIDE the actor must use await to access them, even for what looks like a simple property read, since the actor might currently be handling a different call and needs to serialize access to prevent two tasks from mutating its state simultaneously. This is enforced by the compiler, not left to convention, structurally eliminating the exact class of data race that the same mutable state inside an ordinary class could produce. @MainActor is a special, built-in global actor bound specifically to the main thread, and marking a type or function @MainActor guarantees its code always runs there — essential for UI code, which must never be touched from a background thread.
Declaring an actor
Even balance, a simple MUTABLE stored property (var), requires await from outside the actor — genuinely different from a class, where the same property read would need no await at all. The one exception: a constant (let) stored property can be read from outside the actor with no await, since it can never change and so creates no risk of a data race.
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
let account = BankAccount()
await account.deposit(100) // await required — even for calling a method
let current = await account.getBalance() // await required — even for a simple readWhy this structurally prevents data races
If two tasks call deposit concurrently, the actor serializes them — one completes fully before the other begins its access to balance, which is exactly the guarantee an ordinary class calling the same code from two threads simultaneously would NOT have.
async let d1: Void = account.deposit(50)
async let d2: Void = account.deposit(75)
_ = await [d1, d2] // both run concurrently, but the ACTOR serializes actual access to balance
// no data race possible — the actor guarantees this structurally, not by convention@MainActor: guaranteed main-thread execution
Marking a class @MainActor means every one of its methods automatically runs on the main thread — exactly the guarantee UI code needs, enforced by the compiler rather than left to the programmer remembering to dispatch correctly.
@MainActor
class ViewModel {
var title: String = "Loading..." // always safely updated on the main thread
}