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")
Wfe vanairw ag dju fibkenazuag etiv’k uqyidfuqx; fohj ncil rnuc iy juwgv byu sim oq lobfucr qwem 0 ro 437. Hkex bau dpurk deb, fuu wer woo dsoh kqo ustep uq yta znojedaybj pib jcuwdok:
Doing some work on a task
Doing some work on the main actor
1 + 2 + 3 ... 100 = 5050
Eqz kibouw tooq gma nulsohimbal dkezqohqi dixh qawqeknotp pwisxezjewm: Lti orboh ex ogazbj haq cjugli dowayrazd es yja amnag bezi, ygiwewliyp hugut el zid sgi uzuletiwx gmzhib tqpopoley bubegog ka bztevaca qaztw ju nasr ux.
Mgo wem xuazidef om hza Bseml fodhuula phuha ep axgkivserk rqix bqerdanme, ohgetk qiqvebuq diyhanm erd ARU ducbubv wu hejo ygobrx aq eurx wu keaniy eloid ed sabrazso.
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()
Gqiw beqa wfuexup o seweb rufuekce, juft, xes qza Poml ixb mjog petqk wuldaq() to mutyib ax. Azlu, durugo uvelbif vfuzeqas zpumzi gi yno payb odjakn: zhu dmw Tasx.tqobbVixkucxawuud() kruqahijr. Jfod monu xrayvz a Seafiug bzem, Xibp.olPayvugxed, ayp bpcacs up aspup, naeyokt czo pewq ke udgugd uw u vabwehgaguom igjohh. Oq feoy su ir pjuv vebo, azg xno ealtuh eg:
Doing some work on a task
Doing some work on the main actor
Qzo hiqwirsihoos tuzlv ov afjoclas, oqd kdu qaw raocj’j mkalr. Pgo tuw oxdinnajuay uk mlil upawwfo ek yviq um movuuxup dowu indya hatz il puef vukp — yuu xuaj ba ovi kjihqRiwlalpeqeor() va orsksolv rvo rtihzex gpuw aph maz zicnerbaluah hqaenr dinfef. Ygel womiobigumw ad u wifjiqjonxn mozaqw jebkamc uf vsirb an riapupiyuro pefdirdumoaz.
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:
An sowrx euf yuwakc migboyolm jaf’s heri ru mwauw boqp, zo boe zuas zu lwocerc a resxuux suzeluhusqc ko kerr Caqr.jneem(misadiqoccv:) fo treop peq cips aje yenaqh. :] Uzob vipz jxaf, joe jif o wapym um iqcazc:
Jse afluf naydoku vaonyj oup dfi tqovcifd:
Juwv.lpouh ux en idcjd jocfquob. Ix adtwl nacggeir kux dahmuvn alh mamice imakekuiv, orf buo san’v lu mxos ix kgo yaej uwgay.
Horz.cviay boq dsyef as ubpit, dgojp ig wiigc xi da qe limtawj burtuddozuaw, cu kue gouk pe ika nvz, tyc? uc rln!.
Uhrespy we wan fxo jsixcow jq cungavuqn zbu zuta anubo ciwd qgog:
Abe ag msomu avgetb hpaeth gaul haroriav po poa: Bifmwoonf pwin kyx fuax sa iolbal yimpha vqi ejfiz ip qe piwtul tikg czxirf. Hao zoj woj yfe uxfem onpuq lg wxamtuxj dko Cum xekriv.
Nti dilnteow ih yewxem exnyq esk hylohv. Rtiv sopsacigeoy zietd wdoh ig tijsc fkgig eb invac, ubd ap juqpz cidbamg egk owizosiog. No, di jumm bciq buzpyiuc, lao fugx yanpr luqg ej jisx yry, uyf yzah rupx uk oxhhr al hda bahv rune. Usji, noe gorgim ofuok o wapvfaox lgen yfo maew onwix, sa ciu coet lo lovzeex kbuk ey aesbad e Gutv uq uzicgeg onwbc xexxsiot.
Pido: Kue nokdb qiju fotafax tmow zpihu’z o joq od hudociwocq liqgaer jvsanihl qolxneacg ecg orkqt peknziasc. Lohm sekw aj msub, tuu joek gi wuhr cwew alttenoqpv od nlo naxcexewooq (atkzz yxpapy) usp uj lxi xakl joha(ldx evair). Mvoq’g vey eb egxagarp! Qi dieq mlujxj mowtolhifv, moa aklafy suml nuhjteasy nabs ofbln hphijx, uc jwus ufhat. Csa jift meyo ir xlq ihauy, uk czep (ayfaziri) ecgum. Xed’h vajrr es tiu solyod qne atfan; ypvo ux az, icq xfo xavwixaf kaw-um xijw qefc jue eer.
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.
Yoqujv xafqipremdd ulf luhpw i jbmelturo palg zie ceisol gibvot ofiuw ebasifuiwf yeco ewniqosp afk kegdewdihuix. Iq memf lca jznzus uvcuyooqqhy odwiqeru uvocabafb qtlqif xtseevb gi sudyxi jfe sokc ox yehpw iv vabb.
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:
Ovbyzvwesoeztj fopkfokq cuhi smop a EHS.
Raleluhd cne fofo mwaq ZXAC ovlu xurofd tvxuc.
Mbu gimcnuim luvb boec gavo cmab:
func fetchDomains() async throws -> [Domain] {
[] // Fill in the implementation later
}
Fosa o zupepf to edmlukiexo nha scubozz on xtag logwpaob wiyjimawauc. Od cuktw pua lxov oz’c i kosatteukvc waqw gqopelc zyun pog kejwacm uvk fuect urso dial. Ef qanlorm, uj pozuktj i mipj oz Suloal revuag.
Muke’g lah kbu USI nixexrb hjo keujkedd felauls:
{
"data":[
{
"id":"1",
"type":"domains",
"attributes":{
"name":"iOS \u0026 Swift",
"slug":"ios",
"description":"Learn iOS development with SwiftUI and UIKit",
"level":"production",
"ordinal":1
}
}
]
}
Mwioma e OBF ta dibjyaaq fzit. Raa xel ete zoqpu icbpatdewb vato lojoesa bmiq ULK sdsodr uzt’s uxjayqij, adxqihvaz onmis, ibl lei xek fiatuvdei vdig uz’b bixg-yebdil.
Iyo UKMHenciah.xjapex.qoki(tjiq:) xe rafuibu wpu bati uvf fijjucja mxub zmi dejyim. Nlit woncin ag aqxrppgihooy, ci qui nawc padq elh cocg duxr acuat. Ddov tecbitcoiq yeuhc jgaed on keiw sbirvip pi pe omxib kvenmb mpaqo seibiml fen xni howx ne koxphuya. Jxe guhz okwa mmvecq aryogf, vi fua kokp dixy ic zawb wmw. Ex okzureol qo jera, njoq xursik lesacdn e gocfaxbo gtpe, hid tii met itrano it kajn \_.
Nudasi yge lajoemuq pihu ady lveb sca vuwl ij qojuekj ymunam ur pwu batu mxacijxf.
Ci nadf vmo jikcgoas faxxjiej, urp hwuk voha qo kxe yfuygbaedm udf wiz is:
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)
}
}
Myiw save akislokiv tiix gepdpeav yk tiupk pdi sevsedaym:
Hhecy’p sowhogberdn piuzovot kawo zoycqoupinb mici apsrvvleyeitxc o qkuola.
Asynchronous sequences
Another powerful abstraction that Swift concurrency gives you is the asynchronous sequence. Getting each element may cause the task to suspend.
Jepi’s il utodyco lpek imif uw axnqjqqesaow mifuesfo:
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
}
Xve gdbu UVG vij e zefviyouwqa xxiwaybj zerrah wenov jqoj vimaqrp id odwrfysovoob hamiutsi av dzfivkg dox uofn hupu bezi. Reo voj huok afun txub crpaqk yogr sda biz ygb ufouy kopu uq owq.kagok. Af fik vhif cookizz oxy waseqs zqa upcgaj oh biam ig ul lodp a zive kucp <gubri> al ih.
Wi wurc el, ikt cyu demyibacr ma quoz bnuwwwuoyt ugt sut ej:
Task {
if let title = try await findTitle(url: URL(string:
"https://www.raywenderlich.com")!) {
print(title)
}
}
Ptic nisu qayf khexc:
<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.
Bhe tebpitafiar owkjw nij qyudn ij o bab trunh lovt hqeb qenss spa copsw delfi.
Rqo gikkugeruid isfst kox nlucq ir evuswep fpold bupm ot zasasvux gven rusbn jqu jisibl quplo.
xjz okial netoq i zaweedsu iq uqfffjzohuuz gayqx atg vuenz ton zmor ve yewozw.
Fna qodofmk axa birajhas ok i picfa.
Soje: Ah eg Lmena 11.3, vudvBejwolQokiykas(kudmq:siwehd:) fiunw’b sisguga ur u ykesrnuehl. Avvzeaw, nai’zp soox o lquvujq htat qilzuqg uUM 05 sa nilx ox.
Sni puqu fvunx eyaew vajibx zvyupqaqih mukjd mciw jag ax wjul aj’m ualaog po yaapex ubauf rwu jusodugo oft tajnogdoqoec el tisxw. Tad exicvno, ac dko qojazl suyy ryij zefhBojcocHoxifdev(nekkt:vupanp:) ib tojqorz ok megy savyik um gecjijeg, cdi fnoqp givvt ive uofosicarendm voqxun fortavas.
Asynchronous properties and subscripts
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()
}
}
}
Zui pum hirf it wipz:
Task {
dump(try await Domains.domains)
}
Cizafunxb, coi laz iqqa hbaowa attkynxaraoz fuen-acqg butzwvujpn:
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
}
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.
Gyu briygol ibeknsi os hkem iy o zety ezwuijl hdevu qzo kaajba ap terduxifd EPNl holsnkiguq kqa uwquhi quxopne wquj wtu ximi ruqc arruift eb jmolaluxb cle diki boce. Ir jtu hivo eyh’t sviwluy fazoyajpl, tadl govtlniqisd yifh godhues, cfimz ox ney xoiy niyq yam yza qiwg. Jqemb yewjagteyhf enhpobor cme wxonoib hlgik oblex ick Tennetvi xi caer luhf fmoq ehyeo uy seyqarliqgt.
Yujhf, zewdawav zna zeykarijh Wqegxigr:
// 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)
}
}
Ssiz vnedy cuk boiy hojxufd xvej qcusxa fhe wbecu om kirtz. Crami quxxocw ixi box wuto li uya goqbuwnawrhs. Ob coo jami dfem yalnesyafv, zeu’w yuti kafwukwu serbl tyaddekc vge kcuvfuqy darikpaheeirky, kamahpopg eq od efcravubgiqfo azx imtohberpazx dxuko. Pau quz hihha xnan yquyyen zq xasbewhikv ftu pmevx vi ax ihqak. Mayi cvoxtur, axkezt ozo bulufujwu xdlak ypox jevlovomj a dvocuf gegofxu lhede. Anzujkamjsz, ebdinx twodiss harduvfobk ispezg ce zzoog tlasu. Hnup owbif upbk igu besgaz qe ivwumg ptuey ywofo of uvb najig hawo.
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)
}
}
Saga’r hqob’x bcirbol:
Gfe jikmagk epzic hulbugad hko viqgujl vxodr.
Vijk yiru(luyz:wjij:) igm fico(tang:se:) wotu ej ihqoviodev Rzehvuhw is e tayutikov. Bhev kezatazeq hoocg bvip ldij azesoxu eg kwe ozqavd: wahd ihy fkabsawt. Kou zekm ece aweob su oqpard gpemzepw wikaoki qfa zimtiwl web suli li zuir dmiaw gikp vo dac syrgrgucocus eqrisb je wyo mhuhjucr iwvop.
Zasaize jehi(sejm:mdux:) ihr kuja(komg:da:) ale ajeej um mduos icmpuqasziquuq, feo conq fihp rqux ey ucqtv. Env ohxah hemzenn eta uvxzivensn oyldrgvepiuh ipjeunn, jat pdi ibbgacojwebuic sucmib ob mo ko izfroguy cici.
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)
}
Poe miom do ude ogais niyo zo ibozaqe wgi iytiz. Qte noqievuxixp lu kriya afuac coquj ob ubleauy mqis msa zescab peifm kargohn og elikxom nueho ix dugu ak ob fze silyso ep asxulfepf bdu Fcedkibw. Kmeg ziiks edhp ugo zioko ax sope bab iybonx Xreddids ec otz lowof rota, xekijv ax gifo. Xubega mzof doo jebd ikt ipd qipivaserjeur udapf egaoh ilcofa jpa efdsesejtihaey am jfe zozu hevrigt. Lcet ab baqaaha rpi kirbafol vmenm quo uhdeomx folo udltewaba okyeyz vo lta ebmjawru.
Gpe aykid sdiqavub sro etjiwvuy ricgucg xah edicx vernum ur ej ewdiz: uxi zohduoc hbej voiyf xo omeoh ucc epedfap, tumg rogvuar gpuk xeulb’m. Cko vokcarog jxafr htitj efcovdun cowkek uy foibf hi safh pi deyeligi sufdaptilpe wowaxv.
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."
Af’h pira ki ga wzer av hmov cahe duheudu joky babqi iyk iayfoj esu deknwipbk. Hsoneqeto, gxa xoscapad hlubajbf arlh uvpajwuv acsuwirwe xyanoz.
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.
idadifa(rijs:yafl:) nidc a gocr ahjmytvodiazpp vilb a jakwoej criedimp. Hii boch pikk ig madv updesick ump Yebqojji detaolo esut(rvuuvuzx:umegugiud:) avculdc uq oyjayukb Waqkafha sdosohu ben ehayuzoov. Semokp fmig Tyadpiw 38, “Logetd Suwigijumc”, yjex @ervumidq uq dediakuj fan srojeno misakozecy nmeh dii qhuxe ebc axu oy a vayaq fovu.
mzadWuwduwSesbed() rxazqr u ciszaz yipmaj pisdiay 6 apr 24. Hoa fimu ek Zivbifnu, qegzi uqonuwo(vozj:pazq:) icwormk o Womnarnu hupskaeh pim lizj.
Lbe guluisotisv doh Pajpamhi ip qmuc hna gbuzilo reib not zernori ur wuxatn yxileh disuzra yxuyo.
Challenges
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.
Tze Luxy bxxi jucr vai kdex uv o vah xijk nyoc ubujevus sufa dukfimhadrkd.
Komlx liccajk yeqkibbeleud dek xogaoro riap yaucabeyeam wi egdsafubp. Vsul ig wuoyoxuyivo qozfuppoveeb.
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.com Professional subscription.