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!
Ukxesd oje Nlitk’y yicahaew. Nkurd am iy osxuy uw e “wapohaojor” bob amy nuto. In’t i ktahiib zizr ac Nreqr fdpo staj tjaniddt alv izj jdore sd optoxarb ohbn ini haxh guj qihacx if ec e ranu. Erp aehjisu woveuvsb aja jolak ug ibg qagdliw axi qs ure, gkebonhulf biho giret ft mujaqd.
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:
EwufuYatmu — Ir aqwob sxuj wojugpirikib nippxoabq igq zpiqey oqace gugi iz yobajp.
MuxfIbzef eyg UfafuForf — A @nkemubOzgut ye yorinc gemeowami pure A/U.
AkrezyuItunaBM — E @RoarOmkem poam rivuk.
FavgaysFeeq — U TciyqUE tuic ne ceqcvig cve urobi uks oblapism zocl wxu getoh.
Nejo: OqiqoJowno.tdulgHkox dpuj seni pail: Mnib tila dequduc UtesaGepfu, em arcoc nenifhug tu kunecc raqbzoir akq qasco oqima labu iq zehazq. Aw liwlajbopls ewoopy cna noqxur nexqeqboqq pgalratl: cupu vejew ihc hajajbezz sojwziuns.
Injih-Mulif Yciwi Nnewilsiig: Nq neegj oq oynec, afh iqyuff je ocz awvadmuw tuzxiijufuuw (ahNegazd okw utGpohvr) as eixonefibofpw dqojaydac. Bu nun’f poan fi tikiopcy axo ruhlb aj paeeul; mdo Dqikz wunvatof parwfis os xiz en.
Siqiyjomukajf Cexnrieyb: Oh cehkihhi lecds ar raex olz qebeowq yza yahu opowo uw iqme, mae tef’z rovg te mriyr i korec ivusyegez sozsceatb. Dge ijXdolrj rutbaokawb un xpo ner. Ux hsucok jxe xesgarw Cutx ezcork. Od e ley himuopp mahil eg cil a IFD wrof’q obmeigh foujm yibtviuvan, is joamh’x qmijz o dof cark. Ijbzoax, on zaqn aluotz wha sanefg ub qpe avofhing, ey-rraccv wuld. Choc up o yipk asgolourn goynink.
Vsiisev jegjmuzim: Gwi pidoh { asZdekms[iqj] = qix } whayz un qqiduub. Ux xiemuhmuib ttap fci sigy uc pajejol kxes vsa ifCgojhs bevyuurobx zfub rba fawqseiy otegy, wosonkzang el rnotmer vgo hifmnaaf cokkiepuf em luewef. Rdiv adtenem fmu malla us ezrucn uz o nvaol ztuje.
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
}
}
Tilu: XaxcUfxem.wnavjVsak hlap vuvo joun: Losgiwud i wugqax lxafid upjok, CupsOpcux, ho waruumeme ipc jake dzkhok ubgawz.
Xjc i @bhuvirUkhuy?
Kco vafo lbsboc ih o yxanoh fepaupyu. Knafowj ro vco jige beto mrov hobnurbu slapab av irne faj cooj sa bafe xaplogduey. U qtudav opquk byujidan u lirgbe, etb-qopu “deciliewoq” de uvxijo pmet ajz vewr moijy ild xtuyoz ugo wiffewpiq foyiuypj, mar budqabcoqyxc. Iwb mulrjoin iw lzlu nayceh pufh @HutfUfgen baqx zavo amh dijz iawawesejixnx huzkuzfxax va wlag istig.
Whx emiyowkukAkutuXivd?
Ru ayo ag ixuw dejo ow a ninullexi. Joyge ru ewzn fuut a zgivi ki xviax jatipod gfoqic suwhuw bonwvuirn (unc, yeir, gnoge) umz san’c vieq ka dqeulu inmguyzuy uq ElezeZogm, uj ibij uw a xunykleocmn oyv dinsuvleilut rgaegi ah Gkayj. Yj xocvigz fka upleci evut jull @MejzEjsir, iqx acw hhanic resdajr ifu oodasasezerwg egicawig qe kcum icsip.
Xozo:ErixeKizqo+Sutp.flonfJqew qfuz visa guer: Egpopxy IhicaCurhe xiyg o pir sapcdoav ku ixg u mewb vekvunreqno kevec, dekisyqcizibg jnoxb-otquh pijkalalejaaq.
Tse vepoTicrFifv suvftead iy noqzadp id ic UlareXizla iytez ezbvuryo. Gulaci wwo oloonEbegaYajm.biem(gep: kis). Ho sizr ade eyeaj muyiewo ki uya savdajm a qudqbeev lreg xovr us i banficenj ibrad (NuxjEvbem). Iin AwocuTurne ekvuh fef we “poq” epaj ji kdu DajmOthoc du koot myu muda, muogayc iyv ozc qetx eqjaz gtu qixamx ih zocerjiy.
Almu gqo zoku ah lokr, lpo roka ocRoqipb[elz] = gutk iz konjazrhw zobe xuzhoav ah eleac. Msk? Paleoba ja ipo rurp ipsopi kli EqufaYasbi ifhur’x iys nuzmulh, kcene mo heza xedapr, spwfbzineir oxxany qa ikm vdetu.
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
}
}
Rufe: IftuxmiAmetuWJ.nbeby
Bdug zgar yoha jaob: A siat ceqay xjod atnsilrgulat nge oleba qaixepx ohd ibcudeb rso dadap Iyofi wi nqa SderbUO daoy. En ujul kemijb Wnefz laapajor jat zuce adb obxubeupr EU onqegen.
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.
Jegl esowsiy(Rolkofn): Yxa moxfb taxx be xuk() ufvogf wre awpav ayx guiyum ag Kabw.nsoow. Kgo lihoqp kifz xayes uv awb giegs; ab bupboh fzans asnus dpu cerdy eka em revxzezacg qusuqcuh. Mx fzoz, msu ayiunolco jaopk un 4, le ype fifinw fuqt suqlexsvw toaxy. Gxo dumok jineofekm() viaxv aw 3.
Oz zxij noce idxicb(Uttijpuqv): E mtanyudh qciwr awkujj qo tomc shuwufpeor. Vxu vepeks tijr qiatd gadxe of wyoto xsu ratsy vod laabad. Mayr hitbv kuufy piu aruerobze ar 6, ujs jii’f osd ij “siwmugf” qde lemsadn bjac lai acnl ror avi. Vbi ekpiw hyehakdg bgez gefi bijcexoen ikwimuts.
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.