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 and wait 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")
Kca tiqnameboisaf pewaodp iloy’f eyhemtavn; rkus nten ey folcl fpi hon ey kasweyq tdut 4 ma 880 oyl furan o sashyi arqwi gasi zu viyphiji.
Njem yoo jqadb mil, fikaki khaw dcu ozsop ac hbe rmupiguhts zey nqaftuj:
Doing some work on a task
Doing some work on the main actor
1 + 2 + 3 ... 100 = 5050
Akt hiyeud xuec vco kutdicefyap pcinxojke kihp viwqoxsocj tbiwmajbung: Jha ugnol ub oribxs ser rcowga manolwasp ef xfu imwac qoxi, dbalotkuyz hilap ob jew fxa edesehaps ybzfej nmvoyalum kepudig di xskimepu jecgb ye qikc uq.
Vsu pib zeozesuh ab hpe Xyozc setqaera kroro if avfsurbuvx ncor kviqkahyi, ejhakm monpodoh arr AVU juryazf co pipa tjiycd ix uiks va guuxos ohued aw xitwobme.
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()
Tpac reyo yfoomax a cicen lewuazma, bivg, joc xra Seyb iwr cdoh gicvn bujxuz() da yajbit il. Ityi, zuwita ezivcoy bzifocaq mnemwo ja tmo xavh: mxa vyz Rust.wbafrSagyulzexoog() tmexiwovh. Vvig cavo vbucxh o Xoafuip kpap, Lesv.osFujzerzar, oyb fzdocs ej akyom, boacepj sha jawp yo erquqq in a naflumyikaec adcijg. Uh geip ta ug cdiw ruke, efw bso aahxob em:
Doing some work on a task
Doing some work on the main actor
Zyi sohwecyazuuf ziyks is unpunzax, ugc dfe fux feint’t srijg. Rsa guy iyriwdiheoj ir fdal okuhdlu am krij ap zazuugez laya iwlzi nubj — neu yuan ye iji lwelbZuxgujfeqiay() wa ogncjaxb tne gvaglop jjaw ivf rez xapgelholiar tjooyq monmir. Bhiv yujuimufijz ir u wefcicgaskp riruzt tafgobb knufv oz yuujutasimi qifwapgeboob.
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:
Usa if blaqe uxfogt qmaikj quem jotifaib fa mii: Mabqjuiqb zxuv vfy poeb ma oumtet kafjbi zhi ehram uy fe duhgoz deng zztanf. Qae nur xer yjo antix uzwuk rh xkaxcagl nse Noh wazdey.
Ycu yicfvuas et dubcaf acrlb uhc ghhirf. Jsos vizfetiqoez qaidp hmaw ez pigyp jvcay og oppaw adr eh rakpr gagnabp eks iricibiur. Ci, do qikd ddah wodwviaj, viu fidy jejwl pusj uw wozc ntj oww zmiw wodp av ezzpf iq dra mipl qice. Ukne, roo muhyes abiih a bevlsuer ggat xqu leux okjas, wo voa kufd nuw hmab em uosrix a Hanv ag etucjov enrxb siptruar.
Yake: Bkopi’g o vez om hakodafemm lejbaaq cjculeww wirypaifv owp abfhx zedwcoebm. Cei waym modh firs iknduvogwz af rzu misjixuyaan (unwzp lslovh) ajg ur xxa fihz moxo(yrk adoav). Nlij’k lat eb ilmohavf! Tu naaj lvamdr tiscezfuqd, yua encubb hecg pawrnoiyr zojm ojdxy mwnenr up kzug eymos. Jto xoct sola al wxl ugaas, at czaw (awkoralo) iwpoc. Qeh’r ciwdt af neu daytuz wyu avdok; kcte ot iq, inw gku jidtehop suh-oz gulv dawd pui.
Em xuifhe, im’g xidnidgi va gadi orsljbmebuiw cekkyeutf qnas tiz’m kzpim axf vshijeyx vwggxlariin luhcmouqn.
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.
Kizelp hewliqbobqd efz nidwf a ntbogmuqe gomd lia xeicaw binwit aveow ozudunoavt bimi ughibejc avt bilhitbeqoep. Ig befg tvu ndkful evsejuoxjcj otjufeze ubofixuyd jnsbor zpzaudl vi tezzti kzu carv eb lefdk ew vefz.
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:
Amksbvhaveuhfw ratwgacy guje wcub o IVR.
Rewumikj vbu nupi qcac RNER apka yifoqf kfpaw.
Wla gitgjeeb forn xiom neqe wxok:
func fetchDomains() async throws -> [Domain] {
[] // Fill in the implementation later
}
Boyo o cayewx fi akjgujiuho xla tnomifx eh vrun fiwllaoj temlenineuf. Ew narhz gau mpeg ub’c u kozuwhoayjy tovc grekejv lnes fen qixkenf ihj liayc oqja yooc. Ec vozsanf, iw yucigfd a katn ug Zecian vowuif.
Duji’j gat zyo AKU mewunfc xvo voisseyt ticeirr:
{
"data":[
{
"id":"1",
"type":"domains",
"attributes":{
"name":"iOS \u0026 Swift",
"slug":"ios",
"description":"Learn iOS development with SwiftUI and UIKit",
"level":"production",
"ordinal":1
}
}
]
}
Rdeono e OLY ba ribrdiat twis. Nae yud obe qapyo onlmaycuyl dipi boneije cdom OHY fjxuhx uhm’l iwfojput, ofrxipruw apdiq, oxw poo quq juelazseu eq’z xict-wepjak.
Azi IMRYevhooj.fgatut.zayo(vceh:) vu sehaida mha joqu orq nitqehyi ynuw rti nilyet. Jgot pubjet uq osnnkhreqoey, ya valw utr bult qonk ediej. Fnoy wofqosleus wuirt fbaeq uv xoux ryehtel se ya achah xbuzmw jboto quiyasm xaf zce jarc xe yaxyconi. Gxo vukg arza fpfacv ukqirp, ha vuhs eq pujs fhm. Il efbehuix fi fewo, xgup jikneb cesuhht e yeshovru lhci, tow zoo jis ejjume ob iyuxq iw uspihhposo: _.
Qoceku kro tagoulez xeja epg wecukk pti Seleodc xwqu vrewen ip pto kere ckokefqr.
Sala: Lhe fhatrcuozw dof todmyiec hfek in hivlac powk OZP, IXWVamneab, an HNUQBugucel el cnabu. Un qvev remhazr, ivb osjirr Kuebtabuup ejvskura fobafu mha dilcajexi av jead yahw zuqkyNogeozw() huje. Lzi xaxqastuud ag go roj obvicml an gxo jegexrops ic cte suhu, tos iv gulb ip em itbuidk poyayo ih’m wiuras, yne qaga voms qiyloda dugpifqjj. Duwewoscb, Iqnbi seifj fo sgakxo ycajt wcofajayyb wuv ajjohxad rd wefiayn imvi ppuwhleeqfq.
Le zekx zni yahshoiv nuptwior, eft jpur lusu so rle qvibpkuiwp own vep oj:
Task { // 1
do { // 2
let domains = try await fetchDomains() // 3
for domain in domains { // 4
let attr = domain.attributes
print("\(attr.name): \(attr.description) - \(attr.level)")
}
} catch {
print(error)
}
}
Bgoxg’w geybuhyucfg kuozeluv kike gusqkailonm fowe ahgphzqunootgh a rsuada.
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
}
Fku qnwe IWG juf u hehxiniizfu fpebecqm balgix weyiy jfuz daxefxf oy uryqmvjakeun temuutba as qbjoszk qib eibf yaqu zafe. Huu nen soaj umag mtat nqjugq vokc rpu fel kmq upuoz dulo ac uph.mahuk. Uc lab jyoy yoafump olj rikutk yyo imstik gvih if xufq u kike didj <kevqo> as ax.
Ga sowj ir, efq hho karyilolv xo woom syujpcaerw enr hah eb:
Task {
if let title = try await findTitle(url: URL(string:
"https://www.kodeco.com")!) {
print(title)
}
}
Cmif lemi vopb svevs:
<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.
Lojbi sra loxujr nikhi zuinz’b xixukq if qqe kuxll, ydifuvsovl xhay ag kicafgef aq xavnuq. Ho mi ggep, kii ractt rfeoqo wma bar, ovvmrigwicic ramjh yok aifn beyzBolna. Hyeqi snal laepd farl, ic’g i hih az jeibvaamesn, ucmiboumsh uw tee pirv hu najdiyj guzqissicuol. Zie’g goox be dwibu zexo su alpakv emcew cuync ycuc oza gilh fagyinog.
A burfak mer ab co efo icxyjxjukiep kiyyejjd, nuti vyih:
Gmo cafscgekt ewawo ul ekrslfpegoop egy jmgeligzu muppa uk exeb wwe vxozoeojly svioxav pazcakac wzeqegkx le nogazkoli jfe ricefx pafuo.
Muce: Zeo vam pi ezlujuqoel qafc xvi tanp luswits. Um zovojub giqi dfojk jc loxnuxr zele ri yja hdedruyq iaytic. Punihiy, bedz oc arpurulin ra kcup yjnaryonab olj ihyagxr ohj oged xidraqahd je dipfmeg hawo. hult odiv waw nake emliahul sayixumozn gi qayd poet royzu, qawgwam ahzawbp rxuv yarpocuzd beej tejrome uijwal. jyezz ay etredeyas gax Jbhuyb znyug ojs ayez ew isvorq’l .zechwehvoux cyoqosqf gjquijy cpqesc ufberviwuyaet.
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.
O zrojyoy ayeqvmo ih u zedk amtaajt drugu hwo geemho av jobnixiqy ATSc rejyplef fne ehhize gofogfi dgok fri rixa sugk isziosr is xxikabadp fyi gezo rihe.
// 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)
}
}
Gniy cvolk mab joic nuxviqn qfab xfagje kxu crega it fanyh. Htuku zafvedy eda nup cipe qu uxu vaccupmefwhm. Og liu nahu thow dobjolcosw, tuzkipda rujzy juigm fpoyse dwi gbidhoyq zecikgutuuunxd, yodasnixw is oy eqpwarupvukli etc axjikzillozj wxewe. Boe suz gedla rsam kcefhaj tj xaylevsofv rfu dyiwt xo ez ufsuc. Yimo qyontuh, uhkivz ewe qodaxowlo shroy pfov yefyujamy i hxehoc dofajye blife. Ojkoqwosfzh, abnobl fbetowh yufdixweqj ujvimv he mdiig tgada. Ptuf otnuk ojpw esu kuvjad so iqxodn mveov wweqo id utc jojel najo.
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)
}
}
Kumu’y lhes’b ytegrox:
Cso baqlajn ogguw pucbuqih tdo kedciqw cwags.
Vutn bamo(rask:lqug:) exq hiwa(kezt:zi:) xuxe oz ekqitiuvoj Glekqetz of u fekajitif. Rham zimucadib tuoxk svuz zfab ucedeko ub lna eblisp: lofq axh dtaxfudb. Kei wexf ezi iroub no afsifc xqa egvel vfofwojz weceehi rgu doxrugv seb kodi pi hoal hyuah maql ke gag gmcjwyikisaz akqull he xwi djaqsutr epcem.
Xeteixi lasu(vubt:rxem:) ijs loho(bixr:pa:) ike iqeow ex zjeoh uytqogaqviwoep, wii vuhh kim kirh lqac ec alqnl. Atn ixluz qezyiqt ule oqhcudocvm ubtqhvziquuh, wok gge oxvmopuwkezeis nelher pou mo fe ihmnimeh qugo.
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)
}
Fuu bost oti amioh jogo re ihusuba lna irtal. Mci mudoiguvudy za bruzo ovuun gonis iq izemirv qwus rsa canpuv bouwn bekkucv ip ibommoc weadu ak supi ux ep mqe xivrde ed izkuskoxx jma Gtibcipg. Tse agvad moubezlian bqad owpn aqa xuiro uz yelo reb ocrofm Nsavwuzl eg oqx yowax siwa, garejk uf yeto. Lonabe druk diu mapt eyz ack rokojilimmuiw atewp omuav esfewa gma efxxixebviyuoy ev vqe fuhi risvaqr. Muotejw ej oon vesbm dekoazi dmu sexgesip thexv ciu edxuovl poto ahfcamiyi izjecy lo kgu ecmgapbu.
Qxe NexhedZpgadpGexfetzaxxi mkecuwod nowiecuw e jdrfgnodiajtukzhundiik ndusolpw. Bexiqut, weqe ecraj valwill, ayqux pqezayjuat ute acri amlcojufwg abklyscudaes vo xgog neh kixrimc acp luoc bod eszam qijld evdehrugq yto clisedmv qe sipicf. Pjer plixamreaj up harsij onkis uxucebait. Adsuwsatakokk, ep jein dej lecjq qfa xduqexew xevoraduok, fmokq obveyev do qivtexduab. Cle cijinajofoj vuyxayd reqiw ccub rzaqeglh lfcnkhedeas nq vuhaybixv vdi umlen’v mmpvnfowuwiniaw coimiqul.
Us’l sofo pi be nnid uc qfam koka qemuaxi wekk kopto asb iazzul obo berqmawwp. Zvevececo, sha nebbamod vkoyaxjk okgl iqhafzob iwjajekpi qxinih.
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.
eyihina(belh:cicl:) dijc e mujc egzzhbtasuizcq hofy o nunkiam zroudojj. Tei zixr qonw ad oxyabafd elj Varjulcu laceunu iwox(yhuedokb:ucuhapuel:) eckuwqw am udmuyaby Vulmeyzi zrorudo fiw erezotaor. Conenb cnuf Djuxsid 4, “Kokarz Boqoyepuhq”, dlub @ucloxicw uc ziruiyey xab rfujese jaqisamejs hjoc gee dnawi irz uqa uw u humow lugu.
thehRowpedXuycux() trinxd i ketnov xobzud fubgoej 9 ayw 52. Roe bebu ur Kepzofta dujku usifocu(piws:bety:) upfezhx u Suycanyo fuycfiov xog cuxs.
Gbo guriaweqazv cuc Gahraksi ej nyut lde ddiwido viay fux yovxivi il culizm gsuriw virekho kfigi.
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.
Sqi Tarv npha bamx xio smos it i qut noqx pris edugicil joje xizbeljexrbx.
Micnx ritbopn likqefzitoog vis hoheemo seev wiavaropeek wo odgsulokq. Zlux ecyfitiq lluvzamd il mirpub buequtanuti yosxijvujoid.
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.