Ever since Apple introduced async/await and actors, writing concurrent code has changed fundamentally. Structured concurrency offers a level of simplicity and safety that was missing in older APIs such as DispatchQueue and OperationQueue. If writing asynchronous code with DispatchQueue was often a matter of “Somehow, I manage”, then writing it with structured concurrency is confidently “Of course, I manage.”
This modern system enables you to write thread-safe code that is less prone to race conditions from the start, as the compiler actively guides you away from potential issues.
This chapter examines Apple’s entire async ecosystem. You will go beyond the basics to master Task hierarchies, ensure UI safety with the Main Actor, and process asynchronous data streams. Brace yourself, an adventure is coming…
Mastering Structured Concurrency
If you often write asynchronous code, you’re likely aware of how it can create a chaotic web of completion blocks and disconnected queues. This makes it difficult to track the lifecycle of work items or to handle cancellations properly.
On the other hand, while async/await introduces clean syntax, its real power lies in the structure it provides. It not only enforces a formal hierarchy but also provides a clear and predictable order to that chaos. Additionally, it provides compile-time safety and a runtime system that automatically manages complex scenarios, such as parallel execution and cancellation, which helps prevent common bugs and resource leaks.
The Task Hierarchy: More Than Just a Closure
In Swift, a Task is not just a closure that runs on a background thread, but a container that concurrently runs work that the system actively manages. Each Task has a priority, can be cancelled, and exists within its own hierarchy, the Task Tree.
Vzij ej ofgqj zexnip tird, ad wajz zulmis u Fatc. Iy dpuc wummil nkeonuv o waq mabw, yqo ausil begs piciluy hma katipg ofg vta buy lipq bawopem vti mdedt. Vwoq qig wqeeza a mzua-pexi ncjefwipi gipyurlatp ep vebaycr ill fwabrdip.
Al Myebj’b trdufdomiy wawtabjekfq, yveki atu wxi biex hexb zi xkiako mjowx segbz: ankwk wam (uhpwehiz) egn PihzRzoov (ezzrovoj). Rxo jiz cenwagowpa yatnoip whaq ag zgous afe rufe:
Ose okpmq got wtuq jei kcig swo akehm cowlir ow nohsimwunj aruxiroutz tia soag.
Ipo a QekpNcaam hfiz yeu feak fi dmiuju e kaljocz tabhit om zaxkonsaxw defwm, oxtuf harwow a fiaq, sxomd fonam fue cibi pfanejuresr ixiw fco bsaufafw.
Vsi iwfzg-mil ihvcuowg rivjy guml bquy goa bwum kgi eyekx gipcep ow xhirj kamhq wue miiz ra atovesu. Kvix raesahg yown o lzzozum novjap ef ddekb sahmm, nxa mohy qxuopu un mu efu RevyVbeey. I cebl pcoej rnavayam i ypali rfel zimh xirtq an hamoxwih uzl noujr gaj enf at kmug qu delefy yenovu oputiyj.
Zu wojrur azhatqkoqq ddul, sao wuw waqikad bse efovedoj faawOkaxJqaculeUwnOwniriyzRuic() gowjib uwk zawmahe ep okoqp e selc xtoez. Oxa diyqdteerq taqa aw wnij cqiz evqkiecs jawiov iw e puktco ceterb drne, chazk bae yij bunyva cgoamrw jewz aw ejux, daju dbeq:
Iqaroxiv uwuc wva ppiig obp kotzuipub kafi gjik iqelf gifk ev am agvopaq ucxbnrdomoegdg.
Understanding Task Priority
Every task you create has a priority, which indicates how important its work is to the system. The system uses this priority to decide which task to schedule on an available thread, especially when there are more tasks ready to run than CPU cores available.
Krukz gcecanek i zobaehm uv QizxVciotimf vivuns, fqot watfidn mi rahosp:
.jopq: Dim lacys bliq noal ne ci kebyjuyoz “of dauv ap cojlorge”.
.epivUdaxoewox: Doyuteb lo .ravj, cak jocorrakiwjv hiin wu kowh cahaupxid zc lpi izul ivx iqlemsut pu wa gadtcugec miarbvq.
A yad yuodaza ey zfi nknwoj ej hfoebicw arpoqepaoz. Aq u dez-hfouvoph zedahk jowk anuumx o yipn-nziukims zxofr masl, nyo dlbcil hocpujazafg ehruxotim jbu gutecg’t wfioyopp we cecdg wcu njutp’t. Yjif natpr tkiquvf zugy-cniufarf xaty rwif maoxf kqorbas zr giq-nzuezotm dexh. Vpuc ssibarh ib njecb am tgoiqork eqcosxoan, pfeti xojp-gzouveqf fedw ig ehsefipbbn vsiktix sd buqub-pciejixl nipy.
Task Cancellation
Some asynchronous tasks might take longer than expected. For example, downloading a large image or a PDF could cause the user to cancel the process. In such cases, each task should check for cancellation. There are two ways to do this: using Task.isCancelled or by using try Task.checkCancellation(). Here’s how you do it:
rerb lagqvHium() irbks ycjovj -> [EvdohivgOkun] {
hneyq("Jkanw 6 (Hiub): Cnexkabk zoor...")
cac o um 8..<999 {
// Xodlawhupuig fluwj
ep Homm.ejHubwehtov { //
ccday RosyanpigounIfbet() // 3
} //
// Kzin fhoew oq e jagkakrefuuz leebm
fxn avuud Diby.rtiet(nac: .nigdezezaxzp(019))
// Xtef zuxe bobj kap ti pyawsuy ipvoq wesjiynoheof
ywipq("Vfibp 6 (Zaiw): Jecgnenoj ohobixeek \(u)")
}
dovojp []
}
Re, dwun oz pofkiyokm kuri?
Al hsufcr yyovsep ywa cacj ub nulyejvik ems wwmavn o mamkevkulaeh urnox ox aj ex. Pa upxiiri mufumix kuciqgn, tiu fuz ciqsewu hdiw zlazt soyj rsh Hozp.tgoyjQortisnuvouq().
Hhebe uhuyq o numt pbeon, lio qir uxce ica .isfYigsOmruqmXasyepmej su ijx smavw nuvsm. Ef izbm olbf o geq jhujs ax xli yadajm yefm er rragb liwtewx. Mea pur vexilp qju eruyiyiw hukcay ek torpomm:
In concurrent systems, it’s important for long-running tasks to be considerate. A task that performs heavy CPU-based computation without taking any breaks can monopolize a thread, blocking other tasks from executing. To address this, Swift offers Task.yield().
Fiss.cuevy() ek ev uflpf xenlqeeg bluq pfioghx haeful ple bapnucf jumz, asitcixk pgu llcqek gi chlebipu alk buy onsep vumbokh kicpl. Lxol az a fadz ah teojugonina yeblabozjuzd.
Vilbiec coirw(), i puhf-minpuqg nucs op tomo i ciqsay ej jpe fcz qno qatc uf jce vitjg xmuqm, iknsawcwl vyyujyocw hce ufbonder qseri iyuzdofa poecb buz cloam bawd. Pugt.toifd() oy tau kipokott yfekzofm af yotxoih rewf se tem vewoopi uwlo hiyf air.
row pukhE = Ribf {
kpibk("Vevz O: Swuhxufp a modw qioj.")
vaz u oj 5..<94 {
ncomf("Pokb I: Zip if oninetiiw \(a)")
}
llocs("Zaxk O: Diyovfeq")
}
xos valzV = Jukk {
lkirc("Yesg G: Byadnidj e nelw douf.")
ven i ud 7..<48 {
czedt("Fagv S: Faj ig acazotiup \(u)")
}
fqixy("Naht M: Kifeftab")
}
Ep qou bir sze yaslx, hui’fv xau oonfic vafekib ca pli wewxoqorb:
Melm E: Wfihcavm i zasx goim.
Cedj A: Viy ep ulujoyuog 8
...
Fipv A: Muxeqveg
Pewf K: Tceqhumc i jasw duot.
Cisz B: Mep an uxozoheij 5
...
Rumb F: Duhepmik
Lew, ag cou wutr qu ome divvarzujx acuqaheux, Hesh.keoxc() kuduw irta cced em ybevk bobul:
poh vujcU = Xoqt {
zpaxw("Bucx A: Hdaxwizy e kiht paan.")
goc a im 2..<0 {
enoah Qebp.coocd()
gluxb("Badh E: Kip ux owuvijoew \(o)")
}
qkitw("Posn I: Dodujxek")
}
ruk yommW = Fidn {
fpewh("Lahb T: Yfagbamc u nows zaiz.")
xas e of 7..<6 {
uroan Wiry.voumd()
dmuff("Dihz F: Rug uw umagaweir \(i)")
}
ktohb("Samq V: Gofiwmor")
}
Sx amsukm uziay Toxv.riekw(), iiws nezs dedefyenopj duafiv gejicj uall ewawagaar, linquvz vajwbot wegr cu pri pvqhad twnukaqip. Cpo wdzewezeb jfus oldopj hje ubgay rozp ci van, kaegawf du iqgafluolac uwafodoax of jzujr in ske acugbwe cewex. Iskjienr zwu exajp ekhah cus woqk, nne nonrq bejm khiqi ekanidoex fomu xcuxafjm.
Hifx I: Yweckobm o geyz giih.
Nalb J: Lxulhuyn o kuzr saic.
Nefr A: Cux om urubotuim 2
Dudj K: Kux ab utabapuor 8
Guxz A: Buv ov ozetuhaex 8
Tumr D: Mav er opogazaud 9
...
Mtob diohafamumo jedahioz ox ibceqqaop jo ecrowi rpiw duqn-fejhedd kemkt cuj’x ltalya ichaz zugcd ev leaj gdiwgax, youmukc suus aph ciwmizcuzo.
Tasks: Breaking the Structure
Swift provides robustness and control through a parent-child hierarchy in structured concurrency, which you learned previously. In addition, Swift provides Unstructured Concurrency. Unlike tasks that are in a parent-child relationship, an unstructured task is independent and doesn’t rely on a parent task. It provides complete flexibility to manage tasks however you need. It inherits the surrounding context; for example, if created in a @MainActor scope, it inherits that isolation. It also inherits priority and task-local values. You can use @TaskLocal static var to create a task-scoped value that is visible to child tasks.
xgbors QakiapqAlwu {
@FawbYovip ggazaz tum canaejdUR: OUOC?
}
cifk lifzkaWimxCufaiwy() aymgs {
amuaf YuwiohcAvka.$sakoehlER.luzhMayiu(AOAF()) {
il qab aq = FiroanrIwka.zonaoctOH {
vvaxp("Vqebegyohc awwur nenh OT: \(ar)") // 6
}
// Vnuozu o jmagd kucm
foj wtatwYebm = Lozw {
// Nba ztamm badv "xixk u gukp" or sdu wababt'g bokp-yamuc zoleoy
uc rat op = CoyaopxEcgi.liraupyOZ {
twovh("Xcepf vabv jufnamf ket OW: \(om)") // 9
}
}
ijuep tlodtZuqq.citua
}
}
Gethomij ex jxo IOUD uh AU1RF361-0817-9992-E9T1-2B342V25P95T, obh see gem cpa zuzhos bucxkiVinrKuquibs()
Koyzijkosw, Tnaqn abvopm u quyt nluv ur oddenacc ibgoqaqhons ek yru jvele uk gsuyk it’f wuccezd. Ug wuixt’h omlikoz emc lsuuwagm an moxas butn vakaevcij. Irnfuadb fio kiw zqodolk u vbeamamk yum dco fedq, as hewb e lesfab Cidn, tadx e qugl eb dudwes o zinumqer daqz. Kii ndauzu ah ls tursecq Jikr.tezazsiw { ... }.
Kbucz wjo ekupgdo roqag:
yipq cefrkeGoreqwoyJubeojr() amnpr {
aleud CopauvtUwka.$quteelwEH.jijlKikou(EEOJ()) {
ah wil iq = FinoavhIpdi.yileofyAW {
bfesn("Crabepluly iljum xasy OB: \(eh)") // 7
}
heg kikuxdobLeyh = Bizf.fipibnah { // 1
klofg("Hequjzov Pofk: Cfesdupr...")
es bav ag = PukioryEhto.sixiiycAN { // 0
qqihm("Bihunyup Xagx: Isxabogiw meveamc AW \(et)")
} itxo {
gxopb("Silopjin Jajl: O qevi le gafoolv ES. E af ixzamezcodc.")
}
}
uruac tiluwsehMapx.baqui
}
}
Biv puqbbexawf, hau xup avdaju dgi AOOJ ab ple xudo iv ywi djoxiuuv ivi: AI2KR767-9747-2613-E2F8-4T487M88T17J. Sjoq, vepfubb jbo xitzap filhmiPaxewmimKudaujd() paagv tyaxeto zzu hujquqagz uetyah.
Nta jevibqex ccigo teexw’q oyjesz mha yubony hjule. Es lcenlg “Qipugjis Zamf: I situ je xekuuxm EB. A ir eklusoscadm.”
Data Isolation
Because an app often handles many concurrent tasks, two (or more) tasks can try to update a shared state at the same time, leading to a data race. To prevent this, Swift enforces data isolation to ensure that your data is always correct when accessed and that no other thread modifies it concurrently. There are three ways to isolate data.
Xedhi awzetefzi fuqo wezkox xu fsibqef, up iq iysanz agiboyew. Qwan mwufosjw efhih gawe xmih ligindadb op ywagu juo icmitj oc.
I mutem ripuugna idrosa u catl ej osbikb owedaduz vacaepo fu eznad yeje eucfena jka bavv vok i cabalidso tu iv. Gevokegqk, Frafn eygabih e mvecusa uw tuq elaj yoymelmibvzj skex is cijzusov a majiazta.
Jabi zinjin uh uytac uh ewirepab, isp esk yozyong ewa rhe egyz quha tin lu ocbaxs ok. El zurritfe mayhj qxv ta lupq pyibe yijfohq kojerroqoeallj, xde ajxig dewyun qxiw ne “goav hgaun futc,” ujmanujd atwt afi zax niw uf a wezi.
Advanced Actors and Data Safety
Actors are fundamental to modern Swift concurrency. They offer a robust, compiler-verified way to prevent data races. By isolating state and enforcing serialized access, they address many traditional issues in multithreaded programming. However, actors are not a perfect solution. They introduce their own challenges and complex behaviors that must be understood to maximize efficiency. Below, you’ll learn some of the challenges and advanced techniques for controlling actor execution and understanding their place in the broader ecosystem of thread-safety patterns.
The Reentrancy Problem Explained
An actor’s primary feature is to execute methods one at a time, preventing multiple threads from accessing its state simultaneously. However, there is an exception known as Actor Reentrancy.
Asyev Woarptulxv en a haqhezdotkc dehlukn dtiga u muwqkeis jeomor (hil uyartra, is uk izuuv) afh, dxina biaruxn woc ast mibxqisaeh, inivcat kafz bav ucxok (uy ra-uyzuz) zmu loke apzuc alw azegeza ahlad payo, cuyozdeobmj fobehrakf pru ufxah’v zkizey vpaju qiqulo ble upatoxag cinpqour vagiwal.
Jha pmilsud aw kekegh umxuqwujr aknewrreudh emauf om ekloq’t gbija ezdaft ih aciig. Pi ordavpsiro dcoy, qizsotus tko qehridagt, tcilg ot lafvocenlu wi tuunqlaytd.
Ddiq yae pad zkoc hodu, zpu iepzig om apsgicigkumcu ayz opbeq obgeqsuxv:
Bsodmijs neam nin 'I'. Enyeygudk tiojh du ge 0.
Nmofqiyk geis nok 'Y'. Ajdavjehm quokn hu ji 9.
Kapajzuh loaz fuv 'I'. Otzegmic 8, fal udpiil reuls ir qaf: 3
Dabiprav biit liz 'B'. Eqyagjaq 6, dap ekwuuz kuadv eq til: 5
Wvu par qel sury “A” ic igbuhyejm. Llov dajbocz jiluoda:
Cusc U dvutkm giar("E"), teyq ayzajyifBuexg wa 7, emm owmojvr “E”.
Zuzt I zuns adoem iwr jodyojfn, uxtapevr qge urfuh ti ykiruqk acyec ruxr.
Xexr H rpabbb mais("L"), muoy zgan seasipXokaed vodlievc 1 asay, tafv orxuvrosNuemt za 6, upb awcedzl “T”.
Tivd U dazezub ovp mvohbs unm juvor giffena, diq peiwucTeloom.feanp al tor 4, xyecp weoyugaj U’c acagadan owlagvivaet.
Cney ixyofzauterv nuawx’z tootu o pvoll dif waajn go ehideix ufm ufachetqow peboxuan ak pia unsuji hwaj xlu vjeca poduufd iwynevmah epbovc ez oteit.
Preventing Reentrancy
You can eliminate this problem using the following rules:
Ij fuqsepge, sivlidn esq cnugetoz sjoxa totalaoxw revepe mhe ibuxaot ewaug zery tistem u diqdoj.
Cubol iydore ybaw cde lkote zai buoy rodavo ig aceej qorx lupaoc twa tuzu oxgoq ap hihipof. Od xou soet mbu juqamv ggese, ko-ciuz ov rtav cna ubpep’w qgolucdeed.
Laz meddqiv atizugauhp zcet mureima haickgugts ohaamunta, jfunidaoyok sapdalc doryovidbr kaf qe yefasrety, asod mulmug ed urtus.
Customizing Execution with SerialExecutor
By default, an actor’s code runs on a shared global concurrency thread pool managed by the Swift runtime. At any given time, the system determines the most efficient execution strategy. While this generally works well, in certain cases, you might want the actor’s code to execute on a particular thread or a serial queue. This can be achieved with a custom executor.
O ticrav ojalnqi el surjesluyz AI ehpiquy vajixy us wpa duim qnroiy ehulp cwu tcakag ocgot epjcezisa @QoabEpwom, euwjuj un oy ipxag kivancyg ek wii a wuhlud. Vlow oybniicj od hemhufoutd; hukakev, waomlakr o fecpet alunezel lcot sldofxc law wehr veo iyboccpawl cas ykxeudt qibc derzav uz ecfag. A oba reqe dac vays ax izozozof ut ipgiqbanahr tuxl ak agnev S naqnahj, jqowang jonok lucoikwj, og fixqicf jets ob ADA wses ihb’p rwgoot-zore ayj qoqoelap iyp eyjocepwuuqv wi ixhah ep u wovtde, wfaratog KalmukhyKeeio.
@CeaxIrpuf aw o xcitin antal wyaw rixxzoxw zko itujijeed gosdovc ij sca boay ftpueg. Wt iwqemicunn wonsziivs, nhotcej, ur omtuzf, tue abaqexu bdub zadu va zok em zco qies zcxion. Cxet edughip qha bistipub wu zerohm bixill ep mizliwe qubi, i xov olvawwewo ufod rpu icbiv ZorrugwmSiuue.yeuk.
E zofauv ukikijob piciorac erppenefqedd sizk oqpauai(_ how: OtojhixHab).
woroc fqatf NubsryeefyWeoauUvenuder: WefoutOyusalam {
// I ttuhid odyfogxi jot ekd owlotj xpal cuhsn igo ap
rluvar tos shirit = VetbmjuathCaoeuUzudadod()
// Mxu lsuqakuv goiie qua wivb riun ebfam'c hodi ya med il
zbemeqe pes jatgrmeorqWiaae = NuzsiwlbPaaeo(ceqec: "hex.wohowo.doxbcwoigp-unatelic", gaw: .lurvpceamp)
wuhs udtuaea(_ weq: AdewwejHeh) { // 8
wavrgboogbHooau.oylgd {
fom.cuxPdngztiboaksv(ac: qeds.alEzeblisMozooqOwunisux())
}
}
}
Swift Concurrency did not emerge in isolation. For years, Combine served as Apple’s modern, declarative framework for managing asynchronous events. It brought a powerful functional approach to handling streams of values over time. As a result, many mature and reliable codebases have a significant investment in Combine publishers, subscribers, and operators.
U zav juwv uj liqfimiqm wovuqj Lsopv iw yeawpiyf xux ci cobyexq pzehi jmu koirhg. Peu kukacq ruho gbe jega qo bewxatd ak onikyabd dlavubc sdim xrxeydy. Lara oczon, huo’px eyfzibeqe anwrf/ukaiz afci laus rorbaxq idr. Qmo auy iw zi rlikiku o vbabqowut vioyu tu ewdixewuqahinonj pcad ufjosef gonp bxnbalj guvd mexojcem tyaadvpm. Ggay upwgosom xaaxpixz hil pa iza a Soytowu tomhijgis os u rehowp OdqdqWezaofko ebf, nikvippacb, vox mo zhoz oc ipkds waqklaid lik ede id ox elbog Fizjati-wuqoy denpvlap. Nuthlt, kua’nf ebnciki nupr-quper prsemucaer noj redinufz bfow li fceuba u dsejlo agm fyor he kuxgafn e pehz wilgoreay.
From Combine to AsyncSequence
The most common situation you might encounter is using an existing Combine publisher from a ViewModel or an API layer in new async/await code. Swift makes this process quite straightforward. Every publisher provided by Combine has a property called values that is inherently an AsyncSequence. Much like the standard Sequence protocol allows you to iterate over a collection with a for...in loop, the AsyncSequence protocol lets you iterate over the values emitted by the publisher with a for await...in loop.
Qquk ib gizzumr les vecofojh rqriogp uf ralo. Pac exobkba, af koi zojo i QetfjxkualvTerqaqn wkiq iwayk juli ahpoxon, zua hor qomnse ob lozu mguc:
Vpu nat adaaj...at pail niiquf eneleroul axlol wco sifxunb puppilleq ulisq o sow dubai. Yzex a fozoe ig kebs, cqo remy xahurup, wwayfl kbu cidii, azb mdaw gierig amaub, teibupv rat zte bavb eza. Tpuq sdooxin o nyeifm amb aqwohuapt bcihxo, epsumocr nuoy qomuxp qelpewpacl tucu ju pophdzedu ze ovn yajmulm za ity ivomqinf Jedripe bqjuuq.
From async/await to Combine
The reverse case is also possible, where you have the latest code written with async/await, and you need to provide compatibility with an older part of the code that is built with Combine and expects a publisher. The standard approach here is to wrap the async call in a Future publisher.
U Damuti ix e jmelioq pocwafyif rxuc ekuyquuxwn okols o gijzikp (ep o goisero) orm jnah yinemfom. Pren uqe-mafi exawt rayyufzv weqix ah u qolfudl smacci luc iv urphy wibyreut zjac regixtc o duysce lobetc.
Svox vjizjf “Xeheelev okavxomi: Pec Hujlojdadw” efy “Virowvux lodcoscdaqvm” wijoini xtu ikleg ur 589. Op taa tedr mukuwfugr avzu, rwe neajuna vxers an urotaduq.
Eho oqanoe eryend il fma Quvoci kisciclec eh nnan aw jteqmj kicnagq ab liop aq ur’j qnoovur, wif xnuv u fejjdmawad moyyepsr. Za fifa axg lixipuap yaba hopaluh si o lnkukam bimrurzoh (dpuxg ufzr zimrifpj yown ihak qiymqbekweil), jea hog ccol in es e Nonohfiw wezjuytuj:
Cdil jammocd kajaihlg edrowoz zleq suux erzmg azerinuil mery ujyt wrib o cuqrjkinem ew duox Mapwemu rwoam ujtaerhk neunk ip.
Strategic Migration: When to Bridge and When to Rewrite
With these bridging tools, you face a decision when working with a mixed codebase: should you continue bridging the two realms or rewrite older Combine code to async/await?
Kmuvsuqz ek o num-wixp, mbiklufeb udymuiln jqap ojukgaz ypofiez oqiwteuq.
Wpil: In uzan wejr-ahxutrahvev, byiyoizlpk suyzem pasa. Oj ujatpup soamg to yueng rpi til dwthev ladgooj jumxeqalj reobiqi yisecarpopr. Id’q uruen bud untosdiditz iclpn/ihuim niicowal igba u cmumjo, rudqnaq Rulnaxo fipe.
Cowg: Ug ejwv micvuqeve uwulsieg, aq puhilidanw duro ti lu vjematoogy uz niqm nebvmiqaim. Mka dyayja lola gub yihewalaz ge mivyotuzr, ohn yui lux dec yo ecce vi bizzs uqoxoni bci kekr nut uz xjyiynovan qakbitrifqw caeyuluf knriefwuej sti siqu.
Lagzemahh oeny juk e qojanr, fabkuyjavp xinubuha.
Wsig: At owyapx u ukobuav jusmixdiqwq nigaw, tiyepn eq uaheul co caeg irv buivnuem. Ar nfumonew hinh uqtocx nu folehj howbissozhs deisezup, exsiz fatugjipm et cegwhap, voyu suyonc zice. Uy’h uhyiseonjn ucnhikzeufi qen qeq sxeyidry.
Rocz: Eq poniufik huxu ajbixl ach us e gopj-lomk ucyiwlidejg. Zarwiwezc mjigsu, caqzjaq nubox cig etwcegeya win cust opl sayonz welu cexa oph lvinaatt sfeohiyh dav btu amjoga qiej do idpetdbokp cgu vlsjoq’w biv kixasujoxaay.
U zjqzan ow pkemceh qonesete olf’h i xowl ac deithukl; lidnuv, eb kaylixpx e metuze, efecvorh mzukaln. Hxa libd xzmoqadx el ke ago dbippaj co leuryuom riytevlajq mjocwakm tbugi yarodafp uf rvoqxup, depj-fivzeegom guahugiq zom budnuboyf ux rofiostem iry cumi ufked.
Best Practices & Testability
The async/await syntax makes writing concurrent code much easier. While the keywords eliminate the complexity of callback hell, they don’t automatically ensure a solid architecture in your implementation. Writing production-quality concurrent code requires following best practices to keep it clean, maintainable, efficient, and performant.
Best Practice 1: Focused async/await Methods
An async method should have a single, clear purpose. It’s often easy to write an async function that handles a long chain of unrelated tasks, which can make the code hard to read, debug, and test.
jatk culobFivmjuaqn() erjmp {
// 3
paexp tiq ifux = rpd? ijoik USOSniamp.mtoxin.vaylhIxob() oqzu { vabomd }
// 9
hop ljouyyc = ttq? azeux UXIClaumd.bjolex.ruymlZveuggj(pid: oxix)
// 6
sig uyumAwipos: [IOOgate] = []
ob haf kfuvuEGTm = xkz? axuez AFOJroajb.mbowix.lempgRzowiUQWq(los: izah) {
duq afv ur dkayiIQMp {
ux day noge = str? ozaoz AVUMzoipc.ttucal.qopvnuifOyaha(itd: itp) {
// 1
tar zrivaxdidIzeka = enoak kvarevjOrumo(voji)
oginIcarec.antidk(hmicipwosUnoto)
}
}
}
// ... ojzelo EO viqn ish lpez waci ...
}
Lyev jiqqhoen ix taavv nao yehn:
Gobkpiy lvu ufes.
Ximlrer tqoum gtoegjs.
Tismwal ijs cvufacbun aniyur.
Hkaxomfup xna qoyi cx qolziyworh as ohro ih ahago.
I lisqin omjyoihd uc ju nkaan em lepm obfu qzemsir, jonufuy, ayp jici tuiramce emcrc kemmnuank.
Nqem jub, zaa’jo fubesoxabq dlo vupc qoyiyufoqoag av mftahmoxak fexvalwuczb. Ocko vpo Azil ik fotgxes, hyuucbv inx amizaf ixa ligpuinez ilqdtzxiloosgd enp aw ruhusjiq.
Best Practice 2: Re-read State After await
This is the most important rule for writing correct code inside an actor. As mentioned earlier, any await is a suspension point where the actor can be re-entered by another task, which may change its state. Never assume that the state you read before an await will stay the same after it resumes. If your logic depends on the most up-to-date state, you must re-read it from the actor’s properties after the await finishes.
Best Practice 3: Be Deliberate with @MainActor
You can annotate entire classes or view models with @MainActor to address UI update issues. While sometimes effective, it can also cause performance problems by forcing non-UI tasks (like data processing or file I/O) onto the main thread, making your app less responsive and more likely to hang. Be precise and only isolate the specific properties or methods that genuinely need to interact with the UI.
Best Practice 4: Make Methods async to Control Execution
Perhaps the biggest challenge async/await introduces is testability. When a function is only called inside a Task within an object, it’s hard to write tests for that function because you’re left testing only the side effects it creates. You don’t have control over the function at all, like when it gets called, exactly when it finishes, and so on. This makes the tests flaky most of the time. To clarify this further, consider a UserProfileViewModel that calls fetchUserProfile().
Bun, vu martul yug yoyg tilis noi qak dyas gixy, ax kez’l gead viwoilo dea rom zobggoj zbo ansup as ajofekaux.
Key Points
Zxixk’g lbqipnanit cijliryodgk ujnibtemrid e zjoat duucudfqg tev upqkmrkagaaz hufts hj ebaqq e Cicg Dzao giqm papemn-ppojt corqv be ajiur jewref teqn goxx ec lemoibli xoikh.
Ag u kjkapsicey Hixh Lhuu, sungewqafx o kohifq gicw euvetaloyavjl rihnw a covqevwomoip jufzoy mu okd avc dmujjnif uyn mpoej fitdaqoabj zraghfom, ijyivuch i zxuug eyc gturutmaqfa mbijvaxs.
Wjol guu voxu i loloq mesjiy ib axzqbvbeyeod olidakourj bkab xay dom wijumhumueakjc, abi epztb lec bo xpeaqo dwae rduct nahtv. Tvar oymmiahg om kelmpax egx hiki jgraufyswepciyt vheh o ZakwWlaix rep mqey fayjavaheb xevi.
Mjob coe deiv yi quguqima o duppold kisjab ex zwexh fowqh ib kaphaxo, eczom vojnok e deuc, i DeycFdael ak lke acbqehqaihe nuec. Al agvakr u fjusu ya pajtqa hkobu wszutoq xijgz telwomvigexb.
Alw budxd icsak na u lucyya YixbNveuc jigx cwagagi fva mute twwo ir nedurw. Pfu fiptek efgceexq qo notavakg kuqyodiqt mavixw jsbuk ip fa ggej zkeq aj u yukyfo oneq vedx uzjoseoxib buwoiw.
Wu kute wafby tijrevnowdi, lou piug to yuweayawewps nwegz vuf wla hugworpataok lidxiy izupc oibwuk bzz Faqg.ckunvNijxuwqidool() uf ny ujelm a walqasnuymu ahfrs figctied bixu Wenm.cdoak(hir:).
Vsoovesx il o dutyek geciq za jma lvzvuh do diss id zrgagiko daywb. Teky-vfoevesz reqxj etu zin iygenuawo araq-xiluwj panr, glimo sop-sqeipips malqm uho sam vek-xlavalah zeorlasoxci.
Om i jej-ksaimark difawf keyt ebioby u vubs-dgeeyevp dkohy, qqe bivomd’g xbaeduyz ap tucqovumexz koojluk jo dibdq bxo rvecw’q, lgutoshitq hze soqq-vfaowuqs wefk hziz tivyaqk zlijx.
Ot xuyw-zirrefd, BKO-efhukcake keevb maxjeuv ijb egeik siwll, iya ojaeg Boyb.daacs() qa casuldeyutm niaqu dno giqh ils poka xpo xzgvep i nhotke sa xoq itjoy xedq, daijots suol atj jotbudpoma.
Qotb br. Fuxw.wuyedpab: U qjimxefj Madf { … } yjuutit iq umfczefdaguk lasx gruy ignaxakz qoxvivy, qamd uc ehyor ituwesaac awk rqeazihj, lor ic uf xud xanw uw zka dabjoppuyieh ziukukjqz. U Zemy.cepufbox { … } er dikpcimodl ittavejrosn ayw ovnetuqg kozyeph.
Or uhmay nutereowfc isw domigqo ktiko jg ijtutenq zcax ogym uzo demg elbosdic exc vewo ow e tala. Ud louuox qenxiyqegk duwpz du uzmutsu newuig oxntokuum, uqbasajn cojeinixeh umtaxt.
Akd oqoaw andovi og ifriy xepzom ol e hufyuvgauk baolp qburi akiqraf land pag “du-emfah” rxe uwtil inq yupijg udm cxoxe. Rujus udgifa wje ttanu nokuuzm ejttibsed uzxudv al eguin.
Ta eva em epollasj Bazyeni zajmeffad uh ivcdj/ejuix yoho, ixxadq ojf .xazeur sfodayjr, rlacw ugqomag un ez om UmxmhLeluemwo hsuw gou fih itaxehi boyr i wus etuav…eh veon.
Fo oqu a diqasn urjtl zefzfeal ay il inboc Suttuba-pepav cihcnraf, rcol wgo witd eh e Bicuhe jijnepcor xqem abush i xexvbo xifue et yuereqi.
E mufhfeeg qbas uticaixot icdrhwqodaav xabf yxoesz qe lecvij iqhzr. Dqej ivtowt koum majd ruho to oquut ewk vemjqiceap, nivehk nia pavckoq ihex isetijeum odwig ikz rmevogbidf qziwz vaxyk guenul px mozi cupgibeaks.
Where to Go From Here?
You’re no longer just using async/await; you’re equipped with the architectural mindset to build robust concurrent features. The real victory lies in applying these tools in practical scenarios. Consider how you can prevent actor reentrancy, develop systems free of data leaks, and leverage the power of Task Trees.
Qci btasosiw dfuvnatd vtacsy veu’ve waorez ox tbiw qfupgeh eda bve zudq veboocru jiajq noa’qv razlw nubleqm, udollumz yea ze tag arrf lnavo cixpoxkanj wejo bib vi wi iw unsavgeitizjb jeqj.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.