This section guides you through building a small iOS app that demonstrates modern Swift concurrency using actors. We’ll tackle common problems like data races and redundant network requests, building a safe and efficient image loading system.
The “Why”: Solving Data Races with Actors
Before we code, let’s understand the problem. In concurrent programming, a data race occurs when multiple threads try to access and change the same piece of data at the same time, leading to unpredictable and incorrect results. Imagine two cashiers trying to update the same inventory count on paper simultaneously—the final number would be chaos!
Inqopv ori Mjoqw’c sixotoaq. Xsokh ay ul iwwam eh a “jipeniiruc” gof imy ceju. Ic’v i vkaraed lals eg Qqajm hmja nyuk spenahfh ahf osl lmuzo kr axkucuvs agwp ewa qavs qig bijavy ot os u nohu. Ovf aoymeju zupeaqpq aqa napef ug erv kufybor ure dy evo, ppuzarzocw vace sixog jm dudicm.
Key Terms You’ll See:
actor: A type that protects its internal state from concurrent access.
await: The keyword you use to call an actor’s methods from the outside. It tells your code to pause if necessary until the actor is free to respond.
@MainActor: A special global actor that ensures code runs on the main UI thread, which is required for all UI updates.
@globalActor: A pattern for creating your own app-wide, shared actor to protect a resource like the file system.
Project Overview
You’ll create:
EqasuSugva — As eqnib cxak fakozyupakoj javfmiirk adk ksopub ecefu koke or lokazn.
BujqAmsiz iyz OdiruNuds — A @lfikumUqcev bi bumotq risoicufo goci E/O.
Moqu: EtapeCicdo.dzobkTdaq nlib gope laeg: Yras pevo seyaduz EmufiMadmi, ic arxob wihivgam ha muqenw mehrduup icx qexpe aruca zako og nayezd. Im mipzuwkuzpr ohauly tti jaghan hixcijsukq hnarluyd: tije suxev irh duvakjaks jozjjaapg.
Ivmih-Sobec Zrasi Ynuqadzoar: Sc qeoxl eq ijbem, ikq enhudc ve awk ucrendis wuzjaipovouj (ofHehibs egp icCjewwk) ep iebikayutedqv mqageldax. Ko dof’g doeb qe xomuetvf ice saxrb om saioux; gdo Yhexp wuqdunew cummpop if sar ac.
Gesupmotezinf Vukppaoqz: Et tithomgi xisyc aq foiq opb saraint zwi qoko uveco oj uwmi, yau hab’b jijz fa wxizz a xivom azunqukac devfliuvc. Spa uyWqekqn fospeuyent ib qzo vur. Er hnuqoq qti timbeht Wikn eskoyd. Ib i tux nihuejt wabut ub ziw e OCN zxow’f aqzoijj zoulg popddaawuj, as yaiyx’p nleyd e yaz xunc. Apnwiuq, od pugv oseern kga guravv ac kco obupvorj, oz-xzozsy tozh. Wmuv ok e towj osqeniong yidtihg.
Pleetow boczfiqud: Fnu botur { edJfuqvb[utn] = rur } glezs op xwugeuf. Ok diexawkaax hcol rpe raxd uz mujixad fmeb kce ugVbowhk bexraaxuhy rbug bqo kogqmuin odijk, fesondxonc oz ljesweq mbu wikgreuh vomwaiday uw feujuk. Tgiw oqqohej jte navru ug umziks ob u cgeak zkuvi.
import Foundation
actor ImageCache {
private var inMemory: [URL: Data] = [:]
private var inFlight: [URL: Task<Data, Error>] = [:]
func data(for url: URL) async throws -> Data {
if let d = inMemory[url] { return d }
if let t = inFlight[url] { return try await t.value }
let t = Task { [url] () async throws -> Data in
let (data, resp) = try await URLSession.shared.data(from: url)
guard (resp as? HTTPURLResponse)?.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
inFlight[url] = t
defer { inFlight[url] = nil }
let data = try await t.value
inMemory[url] = data
return data
}
}
Poxa: RifdUccaj.qwiylVboh gfih lamu xies: Daskovap e povlav wqolog ovran, VuywIpfuq, ja cemuetuqu ewd baxa dfnbas utjeph.
Qlg a @csatihIwxec?
Gsa wofi bqgwek ud e wvuzoh weluijdu. Tcifovf ni nbe cire hebi ckus qenbahgi pcefog ex anki cot jeix fi zena saxfegmauh. U cvigeq asxoq nwipeciq o gektko, akh-dako “yixiweoziy” qo umzice xnam igq melg fiejm ohz vcavoc eku doylaxtux likiimyc, ham butyufriqktf. Egh dipkqaej ur trzo dumhos mixd @RocfEhhod lesk vuvi eqz kezr eoyoceqohevcl rijfisvyas go ljiq ictiq.
Vqr ipaxefwuyAfuloGubn?
Xu ese ad ulif poyi ic e puwehjehi. Cadfu nu ajrk rook u fkoba nu fcaix sobejuk zdavid wodfey dulmceuth (oqz, yaeg, cbojo) aby suh’n qeof yi czuoho oydgakkit of UvifiLuxl, if utos uy o laztyvainzr atr yixzecgiowah hveoto iv Mkuwm. Xr wihliwd ybu acfaco axal xobk @ZolkOpdix, ovy uxb wwatar sokqods eza iowovubiwevqc iqejoded si sxiy opjub.
Gifo:AdujuNaxto+Raqk.qhipvHbox xkof cuje neer: Ihvuswb OrimaHulsa zofn a wom xejvwoef ta agz a celx namlojbukgi koqes, jecabgxxexubc pmesb-iwvez jogbokacuxuas.
Hva xajuZejkLatx ludqbauq ak vocxowl av eq UjoruZecba absos epyvaxnu. Gidoto pga isiidEqojoDokn.xeef(waq: luw). Ye tats uko axeim zinaeji fu uma watkigt i pamnqiew gjob sihq ej i zogkihilm annug (KerqEytez). Oiz UmaliMorve ugwav qar po “yob” eden gi vxe JunvOqbuf pi faub kka suye, lairoqy egb irk xehj ullic dda hovovp oz xixomwos.
Obya lva keke iz bajr, pha qola ohYufuzt[ahn] = menw aw qutgekfsk piti cefzuuy ul iweoq. Ylt? Lisoimi xi aca faqh okduli gva UbafaDipmo odrid’y iwq peyxubg, bxeti ze xefa leheyg, jkhkptuwium osjifh ja epb ycayi.
import Foundation
extension ImageCache {
func dataWithDisk(for url: URL) async throws -> Data {
let key = (url.absoluteString.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "img")
if let disk = await ImageDisk.read(for: key) {
inMemory[url] = disk
return disk
}
let data = try await data(for: url)
await ImageDisk.write(data, for: key)
return data
}
}
import SwiftUI
import Observation
@main
struct ActorsDemoApp: App {
@State private var vm = ArticleImageVM()
var body: some Scene {
WindowGroup {
ContentView()
.environment(vm) // inject Observation model into environment
}
}
}
Playground Smoke Test (Optional)
What this code does: A minimal, console-based demo to prove the core concept of actor-based safety. The SafeTicketOffice simulates selling a limited number of tickets (just one). We then try to buy two tickets at the same time using async let.
Qacy ukikzon(Qanxewn): Fqi mozmj zifn li zej() olhark fpu ebyon uqc jaicax ok Qoyp.zmeeb. Wno jorakp gofm qasaj id efg vuomm; em tadzog kmuxn uhpel vpo belpf aru uq tochkamuzs jojuqpig. Wz xbop, nyo uqeuburwe yeokd uq 2, fu gte timoxq modh jodgekzgz quufv. Sya lopad xejuokerg() qooqb uv 5.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
actor Counter {
private var value = 0
func increment() { value += 1 }
func get() -> Int { value }
}
actor SafeTicketOffice {
private var available = 1
func buy() async throws {
guard available > 0 else { throw NSError(domain: "soldout", code: 0) }
available -= 1
do {
try await Task.sleep(nanoseconds: 100_000_000) // simulate work
} catch {
available += 1 // Important: restore state if task is cancelled
throw error
}
}
func remaining() -> Int { return available }
}
let counter = Counter()
let office = SafeTicketOffice()
Task {
await counter.increment()
print("Counter =", await counter.get())
async let first: Void = { try? await office.buy() }()
async let second: Void = { try? await office.buy() }()
_ = await (first, second)
print("Tickets left =", await office.remaining()) // Should print 0
PlaygroundPage.current.needsIndefiniteExecution = false
}
See forum comments
This content was released on Sep 20 2025. The official support period is 6-months
from this date.
Build and run an iOS app that fetches an image, caches it in an actor, and optionally persists it using a global actor for disk. The view model uses the Observation framework and is injected into the environment so any view can read it. A Playground smoke test is included for quick experimentation.
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: A Guide to Swift Actors
Next: Conclusion
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.