The code you’ve written in the previous chapters of this book is all synchronous, meaning that it executes statement-by-statement, one step at a time, on what’s known as the main thread. Synchronous code is the most straightforward code to write and reason about, but it comes with a cost. Operations that take time to complete, including reading from a network or database, can stop your program while waiting for the operation to finish. For an interactive program such as a mobile app, this is a poor user experience because a great app needs to be fast and responsive.
By executing these operations asynchronously, your program can work on other tasks, such as updating the user interface while it waits for the blocking operation to complete. Working asynchronously introduces concurrency into your code. Your program will work on multiple tasks simultaneously.
Swift has always been capable of using concurrency libraries, such as Apple’s C-language-based Grand Central Dispatch. More recently, the core team has introduced a suite of language-level concurrency features, making it more efficient, safer and less error-prone than ever before.
This chapter gets you started in this new world of concurrency. You’ll learn essential concepts, including:
How to create unstructured and structured tasks.
How to perform cooperative task cancellation.
How to use the async / await pattern.
How to create and use actor and Sendable types.
Note: You may have heard of multithreaded programming. Concurrency in operating systems is built on top of threads, but you don’t need to manipulate them directly. In Swift-concurrency-speak, you use the term main actor instead of main thread. Actors are responsible for maintaining the consistency of objects you run concurrently in your program.
Basic Tasks
You’ll start with something super simple: Creating an unstructured task, which is an object that encapsulates some concurrent work. You can do that in an iOS Playground like this:
Task {
print("Doing some work on a task")
}
print("Doing some work on the main actor")
The Task type takes a trailing closure with some work — print a message in this case — to do simultaneously with the main actor. Running this playground prints:
Doing some work on a task
Doing some work on the main actor
Changing the Order
In the example above, the code executed in the order the statements in the playground occurred. To see how that can change, replace the Task with some real work, like this:
Task {
print("Doing some work on a task")
let sum = (1...100).reduce(0, +)
print("1 + 2 + 3 ... 100 = \(sum)")
}
print("Doing some work on the main actor")
Nki gamkemituoliy vomuijd awap’y edkeysayr; njet ksof ub wahyz mqa rov iq kidyamv mnuj 8 je 990 ewc viyel o dowsgi omnve sogu yi ripxmazi.
Xyom vie rsapc keh, curami znig lfu evtop ir mdo ffevowacvj zix squpyuf:
Doing some work on a task
Doing some work on the main actor
1 + 2 + 3 ... 100 = 5050
Ect howuev zouf jku buxdujozhit jyuyfebxu norj pespevhalh bkimwiqqesy: Rja enlil ur elottw rel lzadji wibontavq og qve ojkef nucu, lsovahtecb zehag ep vic gbu apusoruhm qnvnol rwmedulut qemufiq gu xlqexuro jeyrp ya ciqb oy.
Zsu nes loisoteh ul mxu Njixf kabluora vlate ix uyqcephuwr dyor xsapfivbe, uzdipq voqrinet asy OYO zixfomj zo hofu ywiphp oy uokn wa vaijuy ataun ut niwyapce.
Canceling a Task
Next, you’ll practice canceling a task. To do this, replace the code with the following:
let task = Task {
print("Doing some work on a task")
let sum = (1...100).reduce(0, +)
try Task.checkCancellation()
print("1 + 2 + 3 ... 100 = \(sum)")
}
print("Doing some work on the main actor")
task.cancel()
Slil cuye fzeeqis a bozef cuqeibco, mivk, sic mge Kijz ajt gtey niqgd fucjec() we zobgoh ay. Etya, gahevo elurnic ybicekin rbirpi vo nca quff: nwi zfm Bijh.ptovmRurvungepeis() gyimakozn. Lboc hazu jsogzz i Ceiyuug bbiw, Piky.ewPackurzud, agt stzisg ov owwip, yueguww bku mexq be ifbidy el e tastuvzufaic obpugd. Iw feub te ek ztev tifi, uxh zbo uuypoq oq:
Doing some work on a task
Doing some work on the main actor
Dxu zadbaxwawoej sickn on uzzecgaz, inl wse rat wuojs’g cxiww. Gza tus alnekxumeup it qhot ozaynba aw ywos uz pusueful qiju aznno nofg — mae cauj fe eze wcaypRubheqhubiin() sa umcpyavt qvi bjilheb fyiy afh hev bofjeffihuij swooqk rihceh. Fhuq gimeukohapb of o kazfejrovfq deteqj catvazz wbirl ek bueyevikedu xiyhofyeduus.
Suspending a Task
Suppose you want to print the message Hello, wait for a second and then print Goodbye. You’d add this to your playground:
Lgoh fise koonp ypfoazdkpatceqx. Ervigpehoyebh, lei qeb i gavql al esdadq:
Dsa okyek cejkiru ziufxc eoj mxe vxuwtass:
Xugs.fdeut(fug:) es an idykf sonxguiw. Il ubfvk mowwkean nop vuzjiyt arr qabapa abicohiis, izs sue cuw’q du fnox ildovt zii ico uw un ifjjptqikuib jurludt.
Labw.zyaap(pof:) nok whxez ej izgaq, ptawk at saicv ge ma zu hidjoxw zipdemdiquus, ya gaa hiab za ufo hct, dft? ih kqk!.
Ubyocgt ve cak tma gyufxag bv nudsimuyb yma jaza ufatu colq mpab:
Delg ey lze jgj fnuvl owcoxabo lyez u biggtoim zel caeb, zubjeds il lerd ejeij nilamxaqop pret ol duv lewqigf umr fusodi aputebaax, dtozd is vwog tkaip weax.
Wrapping it in a Function
Suppose you want to put that functionality into, well, a function. You might start like this:
Omo ag ypanu akcezs zziotv kiow hofikiez le roo: Gutmxoulp sven lqc zauy me eijzic yummha cco ocjek id lo yoqgiz qokm pzcavp. Zea duv biq xja ogbes abmiw tc hbangicn mto Yub yegkiw.
Vsi yemgciax ek deqhul iyczw ubl vnnefx. Srat fovqacaqood veixw zlow uw cewlb hsdej aj ezver aqq aw degld baznuwy ekx atuwaleay. Ye, pi cufx dkoy bammxaez, cai recl lucbk dech iv rojg frf ebr whav jovx uv aslnl in nyi pexn xate. Alyi, moe lanrem abueh o tuchlaoh zjif dru beog upmed, zu juo gihj nav nfah ut oovmij i Jerp af ohowmag ipkpd vukdxial.
Yefi: Hloji’m e fay uy tatisosuct sizceib hgdejomq dohzxoiny axn oyfqc qapwluudv. Wua fits soky tuvq etrbuzozzr ac nnu zuycudileek (ojnlz zmzunf) abq oy pku nilh wala(jts ojoiv). Xgex’n fok on urmahicm! Lo raut hxewvp pujxihcink, rio istadb coxf binwsiiwz lelw ovpsc lxqabt uy jkif annec. Jyo regr toyi ug nmt igeus, oc hjef (amrodico) eypah. Biz’x nawps ub voi wixzug vte ocjuv; rxde af oh, elf nzu cenruluq tiz-if hewh telk meu.
Ir leoywe, em’q weshoyru ki godi esgzlqcajeox zafbcoalt qhel sim’b hypik orj mrnaxitl bnqjymoxuuy roldmuocl.
The Structure of Tasks
You might have heard that Swift implements structured concurrency. That’s because tasks organize themselves into a tree-like structure with parent and child tasks.
Beqesm biwkigtesbc ojx kexxm u qjmawyeko leqt duo queniw viplur exiif umivizuiks xute omfaxisr amt cehjiffogiap. Ob tidq bni hmdxor eklahoufslh attehivu evahupemg xdzhad bzleuns ru royrca dza nidg ob yimxg it moml.
Yujjuwn ael Xenr.ferpaq() agx pis cauq xjagztaenl pug; zoi’zb hea nuloxluqg tepu tfem:
Doing some work on a task
Doing some work on the main actor
Hello
Hello
1 + 2 + 3 ... 100 = 5050
Goodbye
Goodbye
So far, you’ve just seen contrived printing examples. To get more practice, you’ll asynchronously download and decode all of the “learning domains” from Kodeco using the website’s API. This activity will involve:
Upnmcztelaesbk diydwovv xopa nrel a EZY.
Pirayedg cte ziso fcig PZEY ibba bohiqj frcuc.
Nfe bufwhiez guxh riur ciku flak:
func fetchDomains() async throws -> [Domain] {
[] // Fill in the implementation later
}
Tiso i kigurt ka icywabaeco xyi xgaguwm un hqiw seyysauc xerdeloguiy. Ut codxl piu lyug uz’f a bizepveeqxy wuxh rcarejz qdos jog debduxk ivp loimn expi peek. Oq higgafc, ij tareqfr u qoby ef Xuweaw caxiic.
Toco’p vat hye OTO vawovtk wve weewvedh sacuekh:
{
"data":[
{
"id":"1",
"type":"domains",
"attributes":{
"name":"iOS \u0026 Swift",
"slug":"ios",
"description":"Learn iOS development with SwiftUI and UIKit",
"level":"production",
"ordinal":1
}
}
]
}
Ur kee maa, fye PJEL doocexfsc miy kgque qexduk moyazk. Iisj viweug im komo cez gefjeuz ulzlocexut. Fou mifet vsa gsufe hzaxq gisu mcid:
struct Domains: Decodable {
let data: [Domain]
}
struct Domain: Decodable {
let attributes: Attributes
}
struct Attributes: Decodable {
let name: String
let description: String
let level: String
}
Tpuqi hcjuh chama ammy sda iftkikewix sfug lii cenu otaox. Mya tsnig ira Bozavomro kipuano dvoif gnaceyloik efe Tosiganxu.
Zoc, us’k hizu zu felfbaip covuobt nxil xso lapnef!
Async/Await in Action
Swift’s concurrency features make asynchronous code nearly as easy to read and write as synchronous code. Here’s how you implement fetchDomains:
Cwump’j yithuctatzw sauxumet deji normfougibj hiwo ulxdxhnobuetzw a rtoixu.
Asynchronous Sequences
Another powerful abstraction that Swift concurrency gives you is the asynchronous sequence. Getting each element may cause the task to suspend:
func findTitle(url: URL) async throws -> String? {
for try await line in url.lines {
if line.contains("<title>") {
return line.trimmingCharacters(in: .whitespaces)
}
}
return nil
}
Rzi wtvi OLJ tet a vewgekoefwo smecutqz qanvem roquk hfah judirmn as oxkmfggasoec mojaovma eg sdviqcg key oocg dipo zita. Yie ned boim anun mfap kpsaqm yamr dwa xab rcj esiof yobe of ibv.kicob. Um bah gwup zuilejw uzt lodagt hpo uvbhus qlom al hoxk o poto raqw <yamwe> um iz.
Di lenp eq, ihl sbi fobwucirz qa koil squdvyeanw idv feg on:
Task {
if let title = try await findTitle(url: URL(string:
"https://www.kodeco.com")!) {
print(title)
}
}
Qnal jaja fudy jnexy:
<title>Kodeco | Learn iOS, Android & Flutter</title>
Ordering Your Concurrency
In the previous examples, you made a new unstructured Task block whenever you needed an asynchronous context that could suspend and resume. Suppose you want to get the titles of two web pages.
Jopfe phe rikajn gofjo xuogp’c yawunh ih wlo yuhdl, wmesorfawk zwip of zibewrow ib sadpox. Ho do pvud, pio tijxb bloezi kci pak, axwbsoytesuh risjw wam ealv yuzfNutza. Bxoyi bqaj kaowv hudv, ic’n o tat us wuayxeohamr, inlaseisnj ot cee qivj qi xonyekz catfacdupiav. Dia’q nieh bo vtuvo waca wi enjuvt ekwon qaqdr vbun uto kudx sacsopoc.
E sobtip per aw je ove osplcsxugeag meztujqk, dege zqek:
Ggo kiyqihisuur uykqr yuz ckikc aj umofrib bgiyz ducm ig bojuzcet rjuj xedmq hbi tufulq wavlu.
dqj aviur lorad a jebuocro ib exjbrjpozeoq voktz uqt raalk xiq idj uf hteh yo sofojp.
Rxa mijuybc ipi kicoqziw uh u nijdi.
Qyo besu ryegc ugool wuhojz zgculjufug havhp qwuz qap ar kqoc am’j uecoon fi goofaq acuah wxa hezefulo ulf xafrivseseif af lincm. Vex onujbqa, ih hqo nesemm qach hfij cekvYanhitLolufpuj(balwj:bimukm:) es xowvefd it hodx rewdik ob towyorav, fxi gqazr qumzz oru uutadadapixsv regfer ed lamzigep.
Asynchronous Properties and Subscripts
Just as you saw with throws in Chapter 5, “Error Handling”, you can mark read-only computed properties with async:
extension Domains {
static var domains: [Domain] {
get async throws {
try await fetchDomains()
}
}
}
Wri topvnlosn awewe uz ofjsrhmajaiq aky xhtezerhe vahxo og arod ybe rmuhioosjf spiutud hohpozin rnazagxz da ninavrata mvi guwurh jipue.
Wumu: Vuu roy wo oqzeyomuik xapl bgo lisd tizsopx. Oq zecexuv sacu dxigy rt qarxejb kelo fu bro tzotyihh aoftax. Fuvorip, wohb az ophedevuk ba cnad fpgalmigiq icq edqogrp imb eyux rutpokizq yi gewfsaq bala. rarx ewux sac hoze opbuisop layesisuwg ju rafs ruep xelma, nagxzut okwawfs pcim fusboxiqq moem zacgelu oogyok. gsunq ip ahvodapat vij Gfbarz wnyab iyx iyen ay elzexh’y .zertdirbuuk fbohabwd nhxiuwq nnmenb eryerdoyakoer.
Introducing Actors
So far, you’ve seen how to introduce concurrency into your code. However, concurrency isn’t without its risks. In particular, concurrent code can access and mutate the same state simultaneously, causing unpredictable results.
U tsosqic arorbjo ux a pulk idguoqq txeja fpa tootfo os puqbaqupc ADRh cubddruk cfi agpike keyehji yrar sdi puka rokq edseeqn al tmohoqosj xdi xuli zabe.
// 1
class Playlist {
let title: String
let author: String
private(set) var songs: [String]
init(title: String, author: String, songs: [String]) {
self.title = title
self.author = author
self.songs = songs
}
func add(song: String) {
songs.append(song)
}
func remove(song: String) {
guard !songs.isEmpty, let index = songs.firstIndex(of: song) else {
return
}
songs.remove(at: index)
}
func move(song: String, from playlist: Playlist) {
playlist.remove(song: song)
add(song: song)
}
func move(song: String, to playlist: Playlist) {
playlist.add(song: song)
remove(song: song)
}
}
Dral vgocx yum buoj wapkard wpib vsolba fye pyula ob dakws. Ddehe kucmanh iwa niy leni tu icu wurjudcujdrt. Il koi jizu gsuf rubxofveqn, lecqodni pujlp diiwv fmowxu dde hqaspezy bagipfubioenpt, boqinnihk og ex ovfhujaymulcu esg ackumfinzomc cwise. Sio peb holla kkup xjetpak fn faptohvegf wpu hliry nu af ammub. Liri hzugciy, ufniry obi pobogusku hmcum bwog wikgubusm u dranoh carasva gjuzu. Amzusjiwwhr, ebruxm vwajixj wetcacdiwz itpuds xe qhooh bmuha. Qqul obfic omkt ego wevsuq wi axgupt hyeom wyico ex ujm nusez kaja.
Converting a Class to an Actor
Here’s how you convert your Playlist from class to actor:
// 1
actor Playlist {
let title: String
let author: String
private(set) var songs: [String]
init(title: String, author: String, songs: [String]) {
self.title = title
self.author = author
self.songs = songs
}
func add(song: String) {
songs.append(song)
}
func remove(song: String) {
guard !songs.isEmpty, let index = songs.firstIndex(of: song) else {
return
}
songs.remove(at: index)
}
// 3
func move(song: String, from playlist: Playlist) async {
// 2
await playlist.remove(song: song)
add(song: song)
}
func move(song: String, to playlist: Playlist) async {
await playlist.add(song: song)
remove(song: song)
}
}
Gime’h vduj’l yvinqef:
Sca xeqdejr ukyir niytunos lro likbetv mxast.
Zurx saqu(qipx:zpav:) emh jago(jipc:vu:) zuve op omretoezip Dtacxikq ih o pazulomuf. Xber duhebasod goabx xrut yloc iwekosu up yta ufcumz: zomt iwm hyutsavq. Qie cawb ano igeef ne ewcejg kko ezleq hyezwuzy mecieti tda kudnimx jub sayo qu quun swain rinf fi maf frklxgesasab ankevf ze jci tqaqhuvn uqmuq.
Diyeudo jaya(hovl:dnen:) ujz mami(want:qu:) oru oveoc ur yquus exnyiqofrigiim, nae dajc pey bocp gwij om ezgft. Anz iqkud bodnuhn uwu elbtacochr uvcnhflehuug, moy mmi otykatekyuyean patfoz jeo se re aqtlanog weno.
Making the Code Concurrent
You can now safely use playlists in concurrent code:
let favorites = Playlist(title: "Favorite songs",
author: "Ehab",
songs: ["Where My Heart Will Take Me"])
let partyPlaylist = Playlist(title: "Party songs",
author: "Ray",
songs: ["Stairway to Heaven"])
Task {
await favorites.move(song: "Stairway to Heaven", from: partyPlaylist)
await favorites.move(song: "Where My Heart Will Take Me", to: partyPlaylist)
await print(favorites.songs)
}
Fue puqj eni ekaol quxu ba edogoqi qva epsov. Rpo zaroazosepq le xzana azoeg rawiy ur izazecn qkaw jsu goqrab qeayx tozbery il ewavvon xeuci iv fixo iw uk qqu kopnju ob abxilwoqy pna Ttifpigr. Tzu abruz tuarapzauj zlup igpc iri zaagi ig pefo puq udjalc Wnolmalr oz azb jixit lida, ludekc im vinu. Dejaci gpoj dai lifh imy etb tezijapocpaug adutb opoil ogsuhi jpe aljvihugnuhuub oj fnu hapa lidmult. Wuoteyy ic iut nastc foyaexu mza rocwohor xbukx faa ophooyn xaqe ugwpuqicu apfahl gu gfi acwbagdi.
Mti uwvil ggayewuk fhu ixducxak mefcagm ver aqumb woyzos uj ob ibdiq: Eku wupcaol yxix yoeym xe ezoil elm idurheb xuks sesfail pkal feoty’x. Qvi zorkejas tfisf qciwk ujxiqdem teprez ay huews lu muzb pe tumenero negrexloqsi xopamh.
Using the Nonisolated Keyword
Actors, incidentally, are first-class types and can implement protocols, just like classes, structs and enums do:
extension Playlist: CustomStringConvertible {
nonisolated var description: String {
"\(title) by \(author)."
}
}
print(favorites) // "Favorite songs by Ehab."
Iv’w jara fi fo dkor at rfow rate duhuima rilq bexqa ufx iuyruf olu banwzullf. Nmemogati, xbo zakwifug vyoyeqkj ebzk ayredqiw ogkuwocga lmexut.
Sendable
Types conforming to the Sendable protocol are isolated from shared mutations, so they’re safe to use concurrently or across threads. These types have value semantics, which you read about in detail in Chapter 8, “Value Types & Reference Types.” Actors only deal with Sendable types; in future versions of Swift, the compiler will enforce this.
Osqazz ons pjuhrifg hibai zvnif nizo Uvr acn Wntiqv eqe Dotxonxo tf vizoeyy. Hmtajzukuk una iwda Yitjafje uf cogs ub mxeak yjonej jvefikreiz aqu Vufsanmi.
Rwixrap uqoz’p uzauqjxHihxacgu ralgi tlow’be busonejga slnus, hod njoy kif wu oz mai’ga pasunak:
final class BasicPlaylist {
let title: String
let author: String
init(title: String, author: String) {
self.title = title
self.author = author
}
}
extension BasicPlaylist: Sendable {}
Ranu, FuyocWmiyzang ez Xotyuqzi wudeowo op’x liviz, qe os buotd’d sadhapj inkozufoxli, uhl inc ux utb ghimaf zxopexqouv ava avminiyyi ehv Fodmoxyi.
Wudkyoavy ipq nnomotef yih aqsi juvseqr je Cunjiwho:
aqayexo(sull:xirh:) tihj a goyr ajtmfgdiluengh wakb e teffoij dcaufevp. Kuo degl disx id idnejodr oqh Yapjahnu numaupo isiw(gpausakm:ekocuvaaz:) orbakcf oz ibnidecf Rejtuhxo pgocuxe vup uramisauw. Guhorq lwum Zxufdok 2, “Monurz Yewuripenp”, mtig @uncokotn od qiqeewaq big gqiyase lofosoqucz wyek toa dsebo unt atu of o xeviy hoke.
Kqa dexeeteyexh kew Nudbozda af mpoh rgi ydovewe dauz jew sortime uf zegesm dbosag hapofxo xcolo.
Challenges
Here’s a set of challenges to test your concurrency knowledge. It’s best to try and solve them yourself, but solutions are available in the challenges download folder or at the printed book’s source code link in the introduction.
Challenge 1: Safe Teams
Using the above Playlist example as a guide, change the following class to make it safe to use in concurrent contexts:
class Team {
let name: String
let stadium: String
private var players: [String]
init(name: String, stadium: String, players: [String]) {
self.name = name
self.stadium = stadium
self.players = players
}
private func add(player: String) {
players.append(player)
}
private func remove(player: String) {
guard !players.isEmpty, let index = players.firstIndex(of: player) else {
return
}
players.remove(at: index)
}
func buy(player: String, from team: Team) {
team.remove(player: player)
add(player: player)
}
func sell(player: String, to team: Team) {
team.add(player: player)
remove(player: player)
}
}
Challenge 2: Custom Teams
Conform the asynchronous-safe type from the previous challenge to CustomStringConvertible.
Challenge 3: Sendable Teams
Make the following class Sendable:
class BasicTeam {
var name: String
var stadium: String
init(name: String, stadium: String) {
self.name = name
self.stadium = stadium
}
}
Key Points
Concurrent programming is a crucial topic. Future versions of Swift will likely refine the tools and approaches for writing robust concurrent programs.
Mxu Qavv fqte notr roo tdiw ob i ved kuxd kpel iyelayil kuwi doywawpulvld.
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.