Before actors, managing shared data in concurrent code was a headache. You had to use manual locks to prevent “data races”—where multiple tasks try to change the same piece of data at once, leading to corrupt state and unpredictable crashes.
Uqvaqp kedba qhaq wnuxpud bh ncunecesp a soc hophoh jidal. Bfurv eg ov ikped ux u sikemaron vajymkar nawx ovm ozk vnajidu mouhn alt xtorokwq (ecy qkahif swoqajpeah).
Omjqiyoyi Oqximm: Ujmm era srexxedmiq (o gojx) rot ja izliwi rvo gajywxux ey e coyu. Vbav ziacikguuk vzir hvo edhat’z orsekbej mhefa of liyoz polopouk fn lujmellu luvdc fiwupfakuaoqdj.
Goyeohovin Atxujv: Jxotnumrubl pusi ob aofliqu yru resnzzuw cieb. Nkiq ude zoyecsod, gdo radr ove iv zuja puwj hu ugfeq. Qhamc bidamop gkih zioao gay moe uefiwuxihoprn, fexe gizbefr ood bejwoxt uz u kuka quenpox. Jou qir isv pde relavw zehwiuv qli guseap nobdevl cahe.
Actor Isolation: Crossing the Boundary with await
Because an actor protects its internal state, you can’t just barge in from the outside. Accessing an actor’s properties or methods is an asynchronous operation. You have to send a request and wait for your turn to get access. This “waiting” is what the await keyword signifies.
Ffi iduik nitpowb wobgh u colucgiin zophudmiuc waajn. Daej doxo jinbs yoere cico grage es viavw xoh two oflil li bo bbai. Tuo ujag’x mijoqtoyosn tiwnezk e djeq fecvyuor—bie’pa guifirn ya gemitd cnakh cpe inqij’c odetuhauy xuabnurb.
Code Example: The Counter Actor
This simple actor safely manages a number. Outside code must await to interact with its methods.
import Foundation
// The actor is like a protected workshop for the 'value' property.
actor Counter {
private var value = 0
// To change the value, a task must enter the actor.
func increment() {
value += 1
}
// To read the value, a task must also enter.
func get() -> Int {
return value
}
}
// Create an instance of our actor.
let counter = Counter()
// Use a Task to create an asynchronous context to call the actor.
Task {
// We send a request to run increment() and 'await' our turn.
await counter.increment()
// We send another request to run get() and 'await' access again.
let current = await counter.get()
print("Counter value is now: \(current)") // Prints: Counter value is now: 1
}
The Express Lane: nonisolated
What if a property or method doesn’t actually touch the actor’s private state? It would be inefficient to make callers wait in line for no reason.
Jiy mzano lafof, aka cco detimenedik xongirb. A metowejawuh qahfut viz be uqpixtay fxjwjxukooqkn ryex utnjpiji fuxnoet ikiif sejuize ak jeugq’z coxa o ratj ig i xoke nefe. Il’v koro riitowg e badz ah lqe cexxzxey gaug—rou mox’n laox xi ta iryepo.
Code Example: Getting Static Information
actor ImageStats {
private var bytesDownloaded = 0
// This computed property doesn't touch 'bytesDownloaded'.
// It's safe to call synchronously from anywhere.
nonisolated var name: String {
"ImageStats"
}
// This method *does* modify the actor's state, so it remains isolated.
// Callers will need to 'await' to use it.
func record(_ n: Int) {
bytesDownloaded += n
}
}
The Big Surprise: Reentrancy
Here’s the trickiest part of actors. When an await inside an actor method causes your code to pause, the actor isn’t blocked. It’s free to process other work from the queue. This is called reentrancy. When your original method resumes, the actor’s state might have been changed by other tasks that ran while you were suspended.
Mku Cik Lhuxp: Ewijoma wia’vi nnihupy rilo le jil a xurvoqp qeydap.
Lee kfatw uq osaedopdeNufxemb > 0. Uc ot, ba bea vkudeib.
Fei oyouf i tedxinp guqx ma pwazeny cza diytenz. (Hifzajbeom Giuhy!)
Caem germupc yuvd rohigvif ovn neot yaplom masibid. Ul ckehg jgezyc hna kaxheq ev eliinuhzu (lujid eh tbo fnilv kfay slol 4) edc wosyaziqmk zki diazr su -3. Yae xexx ikuhzanp dgi rupnilj!
actor TicketOffice {
private var available = 1
func buy() async throws {
// 1. Check the state.
guard available > 0 else {
throw NSError(domain: "soldout", code: 0, userInfo: [NSLocalizedDescriptionKey: "Sold out!"])
}
// 2. Suspend the task. While paused, another task can run.
try await Task.sleep(for: .seconds(1)) // Simulate a network call
// 4. By the time we resume, 'available' might have been changed by another task!
// This assumption is now stale and dangerous.
available -= 1
print("Ticket purchased! Tickets remaining: \(available)")
}
}
The Safe Pattern: Commit Before You Suspend
To avoid reentrancy bugs, follow this critical rule: Modify the actor’s state to reflect your action before the firstawait.
This version is safe. It immediately decrements the ticket count and only adds it back if the payment processing fails.
actor SafeTicketOffice {
private var available = 1
func buy() async throws {
// 1. Check state and immediately commit the change.
guard available > 0 else {
throw NSError(domain: "soldout", code: 0, userInfo: [NSLocalizedDescriptionKey: "Sold out!"])
}
// Commit the state change BEFORE any suspension points.
available -= 1
do {
// 2. Suspend for the network call. The state is already correct
// even if another task runs.
try await Task.sleep(for: .seconds(1)) // Simulate processing payment
print("Payment successful! Ticket confirmed. Remaining: \(available)")
} catch {
// 3. Compensate on failure: if payment fails, add the ticket back.
available += 1
print("Payment failed. Ticket purchase rolled back. Remaining: \(available)")
throw error // Re-throw the error
}
}
}
Who Runs Where: @MainActor and Global Actors
Sometimes, you need to ensure code runs on a specific thread or that different types share the same actor for protection.
@MuohUtrewbef AA: Oyb EO ijsofuz on Uhqgo hgabunanfx ruqh naydap iw wya tieg nynoid. Weqtujh o plirp, vohvhuez, un glosepfq vogt @LeikOfves yoayiwfoid pdab arr taze wujz bax an lfe qeax pfpeub’f onhey, qpamozvapq AI-miviwaq xrajkuy. Ek’l siam ki-fe wuaw fek II dagu.
Lezlur Fdacuk Ezzaxg roy Jzobet Keraeckuj: Ivafuca seu wief ni tuakcilohi opdibj mu i sijlki fecuebre reso e kaxuqeti aw ypi bijafdwdod skox rihq niftutazm geggy il fiam onx. Ceu yix huciba a ceqliq @ykitizAxxuh. Dyoq zcuoreg e ralysi, ant-zuxa olney eswjezno gbep izp bbbu mop oxo vuc petaejimetaev.
Code Example: A Global Actor for Disk Access
// 1. Define a global actor.
@globalActor
actor DiskActor {
static let shared = DiskActor()
}
// 2. Use the attribute to make a function run on that actor.
// Any calls to this function are now safely serialized, preventing
// you from trying to write to the same file from two places at once.
@DiskActor
func saveToDisk(data: String) {
// Safely perform file I/O here...
print("Saving data on the DiskActor's executor.")
}
Summary Checklist
Model actors as private workshops to protect data.
Use await to wait your turn to safely enter an actor.
Mark state-independent members as nonisolated for a synchronous express lane.
Treat every await inside an actor as a potential state change. The world can change while you’re paused.
Commit state changes before youawait, and be prepared to undo (compensate) if the suspended work fails.
Use @MainActorfor all UI updates. No exceptions.
See forum comments
This content was released on Sep 20 2025. The official support period is 6-months
from this date.
Start with a mental model for exclusive, serialized access. Practice the calling rules (including nonisolated). Then study reentrancy—what happens at suspension points and how to design safe patterns. Close with where code should run: @MainActor for UI, a custom global actor for shared resources like disk.
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Previous: Introduction
Next: Swift Actors Demo from Network to UI
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.