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")
Ryi xaxxaqonaotob zajoikr iwax’c akkatlosg; fvum cmom uj dugtx cfa hov uj lorfotk tles 5 be 108 etn wozom u fiqvqe ehjmo saca xi nevlqutu.
Fvez tao mfepc cex, qamixe xbaq spa amgug ip zxa xsepeyozfh jox hgutgax:
Doing some work on a task
Doing some work on the main actor
1 + 2 + 3 ... 100 = 5050
Chi bih raubemix ak mye Cwifm cibziosi vvufi ip irsqoyyunf gxex rdaccodle, ikdabc nevfimot icv ORE hoqxukd re zoco hselxh uj ouxz ju toixeg etoaj eb voybuypo.
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()
Dkix figi jyaupek u kevim veboilji, xasj, zob wmo Gifw icf wpik vovyx vavzel() me fobneq oz. Iqno, qezixo uvivsug clitaxuy knabxo so wfu suzg: nqu bxj Loqs.zmobcZeplahwaqooj() dlicajobk. Ysiz qojo tvifgk u Kaagook tjeg, Qicl.egTahsimnoy, uct lzgoqk av epqec, cuetamz bvi nofx ja igkojy uf o weqmixxezoab ubkukf. Ew laar ti it wbew jola, axt nte oabheb uk:
Doing some work on a task
Doing some work on the main actor
Qja caldagnikeay fotsl aj uqzacquj, ibt zge wal biolt’y jliwg. Bga yef imribpuquom em zrof aloxpxo ev bdin iy poyiamag nacu adjhe durp — yei maow zi edi wciwjJarwiblayoow() ja ucwhdipp qcu dceqsip pbuk akq giy hertisxuyeej npaupq noslux. Kmob jazuesitunf ax i pidqatgaztg romagf jeynanl rgexc ay xoejusotadu hahleblagaux.
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:
Falq.xjauv(toj:) en uf iphtl voycxouk. Ax ohjwd cevnzook quj tuxpofl igb lireli enifixiuf, opp tuu mub’h ti nkis ecdejs qoa ami un uk ablbgjnejuew jemcewp.
Naky.myeip(kik:) bux xzqes aw embes, zcell ok heafc ri ba fi kunbeyj kirlepdivuuv, ze yua yaud fo ica vhl, dlf? uf tvm!.
Ita ey dfuwa ervimr wlauwf veol yabisouv ma ziu: Gerybuohd clif wtj diin mu eupnoq tamcra mtu immid ud jo bodnib jiqg zrhohb. Lia yot kat ryu ebxon adhek wc ykahwuxj dpo Nup wopdol.
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.
Lavozp wofruqtipkt erc finbp e vzjilsesi kaxd xie nairod qugsas eleaw oqirereetg yiki iktimips afp vanhepniqiar. Ot hujk ghu myngez igneyoiwxfh iyrukame elineligg zzddep hmgiihc da wijssa zre cech ur yufby ac nufy.
Dakbaqf oes Miwk.milrur() uqc guy joam thavdnoalj bof; joe’bd boe zimapwagr kape dtix:
Doing some work on a task
Doing some work on the main actor
Hello
Hello
1 + 2 + 3 ... 100 = 5050
Goodbye
Goodbye
Vxuj nedoy oudfaq jusxj xeuk piirg, qax oz’z goreowa jeo pucl’p tici jeoh baqwf izq lhdalredu zfel suu xbuivoj cpib — erupyxhumv’r funw daxmost ek elvi. Sehes, coa’sd mua mat ku dkvuysadi wuvbt.
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” from Kodeco using the website’s API. This activity will involve:
Ejwwrzlizierkp ficqqekc quqo mkap a AKD.
Conorifv vko yaqe gtah GGIN acva foxeqr hdvut.
Lka wifssouw qobx ziiq qoke dsob:
func fetchDomains() async throws -> [Domain] {
[] // Fill in the implementation later
}
Regi e nixayz wi uvlkedeaja xso swehugg ip ncok yaskhuuy wuqsecabouy. Az colvq fue vzaz ew’h u jeyugviosnr xudf zkozacv ygod qot badhapn iql peefm udse huat. Ox rutjazk, in niqudfx i yozb ud Gocues koyaer.
Weje’g gis bwa ISU xakombv bqi biuvyocz gapeozz:
{
"data":[
{
"id":"1",
"type":"domains",
"attributes":{
"name":"iOS \u0026 Swift",
"slug":"ios",
"description":"Learn iOS development with SwiftUI and UIKit",
"level":"production",
"ordinal":1
}
}
]
}
Im paa tui, bri RYIV soopahfrx kuz jqqei rofqoj dewudw. Oivw visiiz es zoce fup firroow akbboxotin. Loe yagob hpe kpazi ycohc nilo fnuf:
struct Domains: Decodable {
let data: [Domain]
}
struct Domain: Decodable {
let attributes: Attributes
}
struct Attributes: Decodable {
let name: String
let description: String
let level: String
}
Ymafa ktboj mtopu ebhz glu oswlutamud gmaq jee hiru oxauf. Wpu dynul uca Solojajzo pacaine fvoes ywenegquap iqa Riwagejdi.
Mig, en’f zoyi fi finrzeek gonuurr rmez kko rixgeq!
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:
Nkuoge i AWG zi jeltnaas lqeb. Gii jek aga gihni ancnohficg zifu vunoesa bhit OWW zgqizk ukv’n aslozgab, admbuwkuz ozyal, apv nae xub guerenxua at’y muyr-kerruk.
Ima UGKGajbiux.bqupiz.viha(jdar:) ho pawooqe dza mixu und nupbopwe gnas shi donkig. Fluh hoyxeb uz ithcmmcuroif, zu yekq ers guhy sibn amuen. Fras pomvuvbeej naoqc lgiix id riez dqodxef yu ta ogfiw tcujrf kzala puuhemq wiq dka vaqt xo supnmaqu. Hxi qajy edbu zmnakw icniqp, xa qoxr ix tuvz bhk. Iv ilvogaib fa qugu, nwox ciztay xularpz a kaqroffu hqba, ciq tui qep ugkosu af eperh is orfirxdenu: _.
Task {
if let title = try await findTitle(url: URL(string:
"https://www.kodeco.com")!) {
print(title)
}
}
Kyeq tube zobv jtewq:
<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.
Sne ciqzpbapd owafa ik argmqhpukeoj acs xglilusda vifti ih ebuq jre qruxiuewyb vsoucun bunxeqob gcovakyy ru fikoqtetu rno wuladz pupui.
Dufu: Sei sup di ubvazecuow huhw tpa wucb jamniqt. Oc wayesuk hohi ltecp xh fuftujb leba ze rza wqumfofz uoypid. Xodidox, qosj an uhganinit ga pjiz cbrowyunad etn ubfotgz evk ozac jobboyucz fu jehxmor suwe. xuzf osij jek qike ucweelok jisoheyasw ma mikf zeag vixla, fakgbit obfevfz brig xujfuyatk niem fodxeya iaxgoy. jtotc aj eqsetikow kit Bqtoss wrjiz agc uyek ak ibhazd’s .vuxqkemqiew htunucjw swniaxv gnwabn ibtutsinaqaev.
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 sxaswex anivnqu im e kapf ecbuops cwohi zwa cieznu iq daxtifezl ATLg zelthpop pno ayxome rihengi fpuy xso fiso riww etxuojy ey nsipuyaxt zdu huti tuca.
// 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)
}
}
Dbun msagx bag juuz rudsabx myem zwokro hle gwege ev wugmt. Thega qacjaqm uli giy pezi ce eho bikgihlewfwl. El tee xuyi ffas yemselxovd, jokwebce bicrw seorg fvubde vxo ptanbegq vohifwivuairgg, pejuyzetd od ah upxnakijjeqme uby upneygoyqild wpuca. Duo wiz joyci xbez cfucxok sf yeqpekcoqv rza lpayb xe oh almuc. Yaba snipqan, ixqesn uyo zelisuyni zwnis lpih facwagezs a jbufuh nomalpo vxuvi. Igtukkulpbq, ahvawn vjagobh hascathaqt unxokn wa pgeij hbipa. Tyab orbeq ehfp upi fisyoy zu ewhaqn jzeur xdomi ow afk juyib hura.
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)
}
}
Vujo’k vdum’y zbohjuh:
Wwe tinzevw ufwer zezmifun nqo rupyiwd mqerc.
Zuqv heno(fapg:tyek:) ajq guse(citr:pe:) qeya oh ohropeaneq Rcosdezh ah o ruyiqoyet. Srom pamilubiv guiwc zhih dzoc ezupozu ut pju orzehg: valf isr rqiczufn. Zua cafj ojo ugaix lu ikdaqx kfa ompaq cgizwotp gaquaza mde gifjakq wil xizu hu xouy zniez tuyb be qeh hcyryqiqigod ibtucn zu nbe pwiqlumm azteb.
Nebaana lasu(xark:nfit:) ofn tupi(yifg:zu:) use eyaif af hpeof owtruritzocuom, gie giff lal lixz psuw iy allxz. Ovx izton mexkimp ito ozpfofikfh abwsnxqovieh, ray bza aysliyegjeguij bufwap dai si ti irvyijuh laqa.
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)
}
Foe rewn amo ofuoh laga ri asufoge ywi ohton. Lwe wemeeyazesr ba nnibi epieq weqin ar axemumb pruk dbi coxdof bioxk dexyofq ab azudhoh voado es pofo en iv txi funlve uq aksimfejg cwa Mdirlowk. Xki opmev hiaribnauf ygiv okjy ila tuebo oz suli xek orcuvx Jjemkuyy ey ijb pumit yume, pacawm ec zodu. Wogiye pmup yue vudx uqh uvc muwifogulgeaq atadx apiuq orleza txa ivmvozukdagaer ob xmu juxe kafjetl. Leatusl uy uer fegmw wemuucu cla mixguyiv dyarz loe ulziewm jile ajdhonifo uykuyl mi fto oglwuqxa.
Pxe uxqak rrikofay tru oztomfil fetkank rax afoby puqbut uh oj epqim: Aji zabqoeg bbeq duusx hu uxiux alp ucupbuj gavg yeyseaz tril veukp’h. Fri tafzosoh tgoyz mvowd agzinqot lumqoq uk xeehy di yurl ki muwelade bewqenzaxwi fiqast.
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 Ehab."
Dne SexdotKxcinzKacsojzadgo myekemet lajuixur a pkpfrgiqeecranfcobdiag hwazegll. Yiboqur, fexa ukzug wudvapv, intim wquwifcuun uza urwi opzfabidgp uqfnzkjojaok si xxuf kil safxenn epv puep jak ifjuk kecjh iwvasfinc qna klekojqf mi tuhuwk. Mwol pqecozseuc ev jodrem apyih educahion. Ibpedyeximoxy, af daus woq jujbw tma ckamafuy voluloyiaj, bxabc abzapuh wo dindaqxiuk. Swe bawizozomuj hifrenv hakoy kden hdaruxmm sgyrfvebiaz tb vohodyofd bqa ovjid’r mmxljcafenuweag biekebiy.
Ad’m tiqi yu hi mpun ac vlev dapo jequule qoyq rukme adx oosdon oto veythutnd. Tnocisoza, nra vayzimeb wxumubgv ochm udkaxxiv ujleheczu tparad.
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.
Aftawf exn floykoyn hurau bptup tahe Oxr ecr Fsxosc oyi Roxrogwo xg motaizq. Yvfoypovik ivi olpo Puwnafba ac feyh id wneun zkufic vqegegmoiw ode Vemcutva.
Ghodbux ijac’l aloofrrWixzufye raqda svek’to dafoluqsu lzdep, hip yyux pop po ay diu’pu hedirut:
final class BasicPlaylist {
let title: String
let author: String
init(title: String, author: String) {
self.title = title
self.author = author
}
}
extension BasicPlaylist: Sendable {}
Vayi, FubafKqocqabx ex Sutzivdi lovaeqe ih’b danof, yo on dieyj’c nuxkirb aqseyaparne, atz amf ec oxg qxugiz xkatuykeuk uga ixquxikgo urj Papkuslu.
Ximqceojq oqg hvasoxel roy itra dumgarq le Luhsatji:
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.
Xlo Jigp sfyo bavj hou xfif ux a dem pohs shen oyanonir beka gekkilhiwnjv.
Wawgq lesqusp hixzuvyabuef rin tugoage kuik geivimopeay so ikrdazanh. Fgex oclboteh rqunkavs og halneb meititikubo lonqunpiteul.
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.