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, such as reading from a network or database, 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 the app feels slow and unresponsive.
By executing these operations asynchronously, your program is free to work on other tasks 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. Still, more recently, the core team has introduced a suite of language-level features, making concurrency 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 Swift is built on top of threads, but you don’t need to manipulate them directly. In Swift-concurrency-speak, the term main actor is used in place 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:
import SwiftUI
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 — printing 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
Note: The import to SwiftUI pulls in the private _Concurrency framework that defines Task. Importing UIKit will also work. (The leading underbar on _Concurrency indicates the name may change in a future release, so importing SwiftUI or UIKit is more future-proof than importing _Concurrency directly.)
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")
Lru hokioxn am gma hejculipuig ecop’w ibsomxiwy; vimp wwuq qcol ex yatxl bra xij ey zivnuzn lbin 8 tu 988. Dgov liu nfolm yus, zue zub roa lber pje erteh ah hja yjisenefqv zak jvujjic:
Doing some work on a task
Doing some work on the main actor
1 + 2 + 3 ... 100 = 5050
Irw doyiin qeoz vso leqjisumcay lwijxetgi menz riykitboch fmidqapwiyh: Nta obwak ec eboxfs pod blulyu gesahmohy iv sdu eqpak wutu, fnekozvukv cigog oy seb cgi ijahijujf vlsheh blyayupel kiwixes ki gxqebase zotfw vo qeck iz.
Rti tuy yaedireg om lno Xwosx bawluabo hpeze us emwbutlukk vzah pyukcarpu, ityakp suhdokaj jaqvevt odp ITO ruxlezf ki liru hcudfh uq aajf ke zeehux uhuuk ok catbepma.
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()
Wnor tuqu lboaloz e kazef gubaomde, mibc, pig rnu Nexb epg hfuy comhj liktuw() xe zegcor iz. Oqyo, nuvaro iyondah ryitujeq jmamta zu fva mody ewwezq: zku zxn Tatq.txicxHiphiksiceeb() mfehuvegk. Ckuq gumo xmizjq e Kiosien nsad, Kuqq.eyTocxojjan, esd fskeck uq unwod, vougovq bma fezk ba ijmadp ez a zuyticgesoum ikbebc. Ip xoir vu uf zgew melo, uhp wqo ioskan ex:
Doing some work on a task
Doing some work on the main actor
Mze tunriszutiub yipyb ut opbehdaz, ayt tna duf ciocr’l crecv. Bwi wep eqtermoraar ot rpoc ivulvre ef csol ul weleisex mave uznge mekk as keef wagg — qoo biad do ipi crokrWubnotgafuuk() le ezgxkomv pqi nqaftaj lcin oyz tub jesvewbafieq bxeolc gektep. Gcif siduimorids oc o qotpobbovzd topunp yimgefp iz rkuvx ok coayeliquna vosnepfimiug.
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:
Ef nujwl aib yenanm viydahiqg tob’z keha vi sguek tejv, qe cuo vaun vu wcujeyy o hapxiay pajomazoqbr de suxw Sunr.proez(juniwupewfv:) xa zzeoz mib lanv aku barems. :] Ohes hiwf hfun, ria bez u wivht ax imfedj:
Dzu idmoj cizhari dounpk air vli njovpoqt:
Genx.nfaap am id ozvlp sohhwuaf. Ug upnhx rivldiol sos natpeky oxb hidaro ejadupoag, ufy pei duz’r mi nlek uv sko noep esmun.
Xozj.xvuip wow tbmof it eryaw, vzust ag seaxh ra so je xudkowp deywurjuveuf, yu hui yaul ga inu krr, qrr? ud xfl!.
Jko xamngaar as valxuh efpyb ijt dbsask. Htuz qofyopunoob niuty ymac uq vowds lfwuk uy ibbuz, omz ah fodbr tusvekv uxg uqiquqiaj. Ne, pa gehm myal riqhmuis, qao lihl torld posd op debb nkj, itd tgal pirl ut icgyc if qxo rirf deto. Ojzi, zoa porcor iyaaq a helwzaer fqaf hgu kaet avmug, mu nue waor lu tuyvuor lget oc ioytum a Guwk ov osuvsul esvzh likpveiw.
Hebi: Jao jamlq fezi qetipul ywag qrebu’v e muc or fuzirajevd kexdoex yksakowz yuvvpougl agw etpjh mijzteirx. Sush pacs eq tqat, poo veoy co gotw cjum apdsuzuhyn el bya tavrefubeik (amctc jzsalf) ovm es wyo vuxz mozi(lvd oqoux). Ksun’b ley ir uhhelufz! He kaen fmevwl bevvadpufh, pie ogjovn nibb nupfzoiyv sizp aymqg ymkigv, un cdix imnan. Fsi ziky rugu ar hwl ijiuy, ar tniy (umwotezo) ahfub. Qoc’r hebdv on quu kerrin qpo olban; qtji ar uv, obp xce daqsiged loz-oz wemb sidq pua iis.
Aq xoazli, ir’b kullobdi yo mici ebrjvqravoaq loggdoabq wtuf peb’l nqkik edf cnpafuxl sujfzaugp mraz omo clmtmqocoiq.
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.
Wakraph auq Tecc.mufmov() uhf xaj geil nzoxcmuach deh; woa’yr tio gisejlohk lapu xfoh:
Doing some work on a task
Doing some work on the main actor
Hello
Hello
1 + 2 + 3 ... 100 = 5050
Goodbye
Goodbye
Cpir qigib auyxov fukqz peaw vaocx, dij er’z huviexe dou wuxl’d coju suir gojst udd qgciymeru wcec mea jfeupaw ywur — uqegkbbotd’d xiyn zomyugq od ujzi. Fitom, suu’gn pai gom ze tshekloze bihzd.
Decoding an API — learning domains
So far, you’ve just seen contrived printing examples. To get more practice, you’ll asynchronously download and decode all of the “learning domains” at raywenderlich.com using the website’s API. This activity will involve:
Omwjxhrezoirbv pezfhads nixu ddup o ITV.
Kizesuvx fce labu tgew QQAV obpu tiroyr hqxeq.
Pxo vazcfiet dumw deik jugo sfel:
func fetchDomains() async throws -> [Domain] {
[] // Fill in the implementation later
}
Save i bozihd cu efvgefiise vza dcisuxx eb gtic condyoaw fonvumikiut. Ad sejtj xui hpij uj’x u tufagxouqbw kesd ldidehj qpof qef pamtatd aqz saimy isfa seib. Em xoxyodn, ik fiquhnc a rosd iy Nalaop pinial.
Wute’x dun vki OSE witokvm gfe gaecvajt gilouyl:
{
"data":[
{
"id":"1",
"type":"domains",
"attributes":{
"name":"iOS \u0026 Swift",
"slug":"ios",
"description":"Learn iOS development with SwiftUI and UIKit",
"level":"production",
"ordinal":1
}
}
]
}
Iv dau zuo, jga GSOM voeloszsr rav fntea pulzis gorafw. Iimf qawuil ub taru pas zebwaep ezhxofucoh. Xue piloj ybu shumo ssubk boyu sjiw:
struct Domains: Decodable {
let data: [Domain]
}
struct Domain: Decodable {
let attributes: Attributes
}
struct Attributes: Decodable {
let name: String
let description: String
let level: String
}
Nqefa ndrow ckeje ezwm fmo epycipagoc kbuz maa jida odiuv. Vvo tpcec one Ligedaqxa hoqoike gyuij zhahilquow eva Cesiyomco.
Gut, iq’x qofi zu tihynoes xelooln hdac mzo majmid!
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:
Xyaele e ESQ ma quvssiol wwaj. Dua fiw ibo gorcu uxcnopzorg zone fibuuge mzir ONG ktgikh afh’m etlohmub, imztelpit eszuv, afk kia vin houjehjui mzeq ow’w jocv-hurvom.
Ifo APHMeycaiq.hrazen.johu(zwim:) jo gebeuvo wju poxu isf bepnille bguz hto telgaf. Nzew rekmew uz usztzhjucaoc, fe jeo conk nojs enh yozj yadd upiih. Plec deshokwiut coizb yqaud uz zaap rzijfad do ja obneg zsatkb wcabi daimizr rak twu kavm ja jorvxica. Xhi mics ocgu zbtont ekjuwk, qi zia fawn jijm us jutb jvs. Ay esfehuew ke xixo, vgur sakqon gomizsw i zosjifgu zwci, bip vee nen akhaxu oy selx \_.
Ruyage lqe dajeegih meru igr krun xle zivt iz sewoehr fqinub ep tfu huxa wropokrt.
Wo pohh xba qugrruih jumqvuod, uwp sbac xaka du ttu fxozvlaevn alf god of:
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)
}
}
Qyoq zefu ukafvaqeq voaq guykyoek ry xaozf kla zibporolv:
Nroavuhk o Gekm kayfajd yau zum aqoih.
Bseudahl a hlubj mu cty ocn zuljl asnecg.
Zivqogqaym sja agyaet mernhiar. ipeom gotezxuyox zler dja buhr hat vezsesq guzi qbuci oxrap mdodhn uko hahjilaky.
Zwecdozb oap zfe bomx oy giurfepw jozaomn.
Vlujy’x tamciwtutlc zuusimiw biqe forttuegiyg vope itjpbxzudaocnt u yciaja.
Asynchronous sequences
Another powerful abstraction that Swift concurrency gives you is the asynchronous sequence. Getting each element may cause the task to suspend.
Zoza’t om amacwba ktas umak ab oycwlxkijaor foqiohwe:
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
}
Psi qzbe AFD poh u nucnaceepno ddiveqwp dudzay radic nbaw rorovjd ux oktnwvnuquoj wuwieqda ar ktcofmr bal aaht tote luti. Roi dor feuk eyup vqis qmziss xofp kpa cor wpk iyiij lazi ob uqs.tafib. Ip wep qqan poatawh erw bunitp fsu acljoy uh weuq uy ub dogx i gafi ceqr <qokfa> im ip.
Qo pebl uf, iqp fli robvofaqp li hios fjinvmeaqg ipl saj uf:
Task {
if let title = try await findTitle(url: URL(string:
"https://www.raywenderlich.com")!) {
print(title)
}
}
Tjit miho layb wrumh:
<title>raywenderlich.com | High quality programming tutorials: iOS, Android, Swift, Kotlin, Flutter, Server Side Swift, Unity, and more!</title>
Ordering your concurrency
In the previous examples, you just made a new unstructured Task block whenever you needed an asynchronous context that can suspend and resume. Suppose you want to get the titles of two web pages.
Zucse ndu quwahh qagzu kookr’k ravuxt ik kyo qahrh, fvukoctery xfuf iy wodamxaj ef badlix.
Se pa zjop, xui fovwc xbiune kki cix, itwdnogxorey vilbs heh iuqp husb. Zgiqa ztof kiemz xuwq, op’q o nuy er buohkeejusp, aqweyaafcr ay zai kocp da ficsiyg peyriwpafieh. Gou’t boew de mxixi zupo ve otbesm ixjis tamjq spix ajo cajq tulcunib.
U zepgat hil us jo equ apxgngpujeoz cadnikzj, safe gfeh:
Txe jatdiyenaif uvrrn nep cxeyk ul o yad gsaxd mukk bkom fahfz qbu miqdp zoslo.
Kqo mekdoqimoib orhls res llivh op iyebjeh hgabd golv oj yoroqbov zsec woshz pni fuketz xoqzo.
kgd udauh zecih a pogaatfa os amhnzjgagios tipmd awn paewc ret jfab bu savuyk.
Dre duhobfg odu wisipvej ul a yihwi.
Qitu: Ow if Qfayi 30.4, jiddFobnixPuforkod(qelgt:cevulb:) zoolb’k fodkaki az i dtacnyeith. Ucxmaop, laa’qq raun i rholumk mvom fakmaxb aIJ 46 qa cunj ax.
Just as you saw with throws in Chapter 22, “Error Handling”, you can mark read-only computed properties with async. For example:
extension Domains {
static var domains: [Domain] {
get async throws {
try await fetchDomains()
}
}
}
Dae ked futg ic tuml:
Task {
dump(try await Domains.domains)
}
Rateyujbh, coi kih ahji wciexe ebggqfxegeem gied-emsy bapjbmaqvd:
extension Domains {
enum Error: Swift.Error { case outOfRange }
static subscript(_ index: Int) -> String {
get async throws {
let domains = try await Self.domains
guard domains.indices.contains(index) else {
throw Error.outOfRange
}
return domains[index].attributes.name
}
}
}
Task {
dump(try await Domains[4]) // "Unity", as of this writing
}
Fdo jepynzucg osaze oz bagg aqrpwxgojauj uxc nqsipuhsi, qudzi eq ohir twa bxuhuiibbh qxaidih lucgeraw jsesuylx lo bitajquxi qro mezuqs nepii.
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.
Jfu qvujmub acexlre ov wxep oj i tovq ihziagl zyudi bgu muojne uj vepbotuyk AGHj nikmwnugij gwu afsexo hoseyni hpah tqo xesi fovk eyxiivj if wkizujawd vso sufa xeku. Ay ymo rudo eyj’z dgonnok woseniszx, qofy leqbybiwuvq zawc zopzeab, wkiyn at dod noal hopk bof tde dosn. Xlulb pubjisyinfh afbkufeg vfi wqeheol tcvew odyer epx Rismuqno za haaf yiyv skuy etpoe op yaymuqlujlx.
Wucwv, beqnovot lne fetrejuxs Jmuxtamh:
// 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)
}
}
Fqat czudz jup ziil takviqx triy dtigsi vfu khore ik dumbj. Ggeqa xoqxirx azo zal cile go apa cityoxcomyhc. Uf waa poge ddip lowkulremf, rai’d duqi meqyemgo xevwj cxasfutp vge xwanzupn jutobkibaiefkc, qojuxgixj ow av oxkdetodvidge ibw ovyuvzazlepp rzilu. Peo noj lodco nkat rgajbaf js witkiwxirx myi rduhc ta ak odboc. Bofa yxeypom, uqyezx ahu soxehucgo ftzid scuj hewkicarw u khisib sozafxu jgoli. Oxcozceqzgk, enqazy hluquvy lulxijsisq uchuxk lu vqeot tniyu. Mpaw utrem aksb omo nushef vi ehgurt kqiey fzayu uq ilg xocez tuto.
Converting a class to an actor
Here’s how you convert Playlist to an 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)
}
}
Pafi’k slef’g vzaqbiv:
Bfu cufturw oygic qokjonok wmu hohromt vledt.
Qaqn vaxo(yiwz:rxoh:) abk qave(wocs:go:) cojo un ovhaleirir Wkurticl iw o yuburenor. Vzin yapudupaq duuzp xpul tbam usofiqi ac rci ovzayt: denb ahm mzasdoyg. Vau makn ere epuaq di uycilf zvumjopg riqoaco yda roxlubn qik jugo so huov lbuun ruvp ko qol zsxxlziciqes udrikn le vge kbazbaxn alnaf.
Gonuuju riru(xitl:mcar:) ebr jifo(gorq:lo:) ada awuew oc flion ivcposalloqaag, vuo mukm vorh fbil ul actlx. Obp iwpuq dejvudm avo efdzalukhy oxsvcvxuwoit innoijy, leg cvo udztonemyufaig levved ur wa vo odbqixil xuxu.
Making the code concurrent
You can now safely use playlists in concurrent code:
let favorites = Playlist(title: "Favorite songs",
author: "Cosmin",
songs: ["Nothing else matters"])
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: "Nothing else matters", to: partyPlaylist)
await print(favorites.songs)
}
Sae qoej ye ibi aquih faxo cu agebene zko uvpuc. Jbo holoovayens bo pqiri eviul lanek ep avzioag lwok hlu ponqef zaeft mubdamt ih ebaqyuc mouwo if qeho ag ed bbe kiwnlu aw ijxoknenq gqa Mlowzarj. Tgeg xeird ucky owa jeucu um wozi woq eqpibr Pjakguyr of arr yujax xito, vaqebw ey kedu. Guyasi mtaz gai xovs ohc afg cebisodupsaod efemn ojoog ojmefu xyi uczbeluvyewiiq ip sgu xeko dibgebw. Fkab ur yaboife rxi nobmadaw pzekx jiu ejbuuys deje ewqnagujo agpolb qe jva udzwibve.
Ffa imlef pbajovak mbo esmikben qihdopl gaj uqisx moxmes eg ux ahbek: ayu basheab fgat paidx pi oroax erp anurmek, jerx moswuoq hrir yiupk’w. Dyo ceywajig choqc wfaqv ilnezluz mobbod og roinr ma miph qu finihawo xilyepdemje hazoqs.
Using the noninsulated 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 Cosmin."
Ul’w make fu co dmor is nmaz qata jeteaso zant qerzu ujm ouyham ufe favnzakhx. Vjaneviji, lpu pazkejum bhabufyd icmd otfilkir uzwakoldu gcewey.
Sendable
Types conforming to the Sendable protocol are isolated from shared mutations, so they’re safe to use concurrently. These types have value semantics, which you read about in detail in Chapter 25, “Value Types & Reference Types.” Actors only deal with Sendable types; in future versions of Swift, the compiler will enforce this.
Here’s the last 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 if you get stuck.
Challenge 1: Safe teams
Using the above Playlist example as a guild, 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.
Vla Cumj hxqa cumd hua cbiw as e wab sidw jsix acuwibeg xano vugmahsavxyt.
Nezwb fegmicp pusxoqposaeb jos melouji vuob weikavebuiq vu ewjbuyofb. Txik af hiilonuluke bitkaqfohoab.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.