In previous chapters, you’ve learned a few different ways to integrate asynchronous code in your apps. By now, you’re hopefully comfortable calling and writing async functions and iterating over asynchronous sequences.
In this chapter, you’ll dive deeper into how to create your very own custom async sequences using AsyncStream. Using this method grants you complete control over the asynchronous sequence and makes it trivial to wrap your own existing asynchronous APIs as async sequences.
In this chapter, you’ll work through the Blabber app to explore these topics.
Getting started with the Blabber app
Blabber is a messaging app that lets you chat with friends. It has some neat features like location sharing, a countdown timer and a friendly — but somewhat unpredictable — chatbot.
Like all projects in this book, Blabber’s SwiftUI views, navigation and data model are already wired up and ready for you. Blabber has a similar foundation to the projects you’ve already worked on, like LittleJohn and SuperStorage. It’s a connected app powered by a server API. Some of that code is already included in the starter because it works the same as in earlier projects.
Open the starter version of Blabber in this chapter’s materials, under projects/starter. When you complete the app, it will feature a working login screen, where you can choose your user name, and a chat screen to socialize with friends:
At the moment, you can enter a user name, but nothing else works. Your goal is to make asynchronous calls to the server, then provide live updates in the app by reading from a long-living server request.
Before starting to work on the app, start the book server. If you haven’t already done that, navigate to the server folder 00-book-server in the book materials-repository and enter swift run. The detailed steps are covered in Chapter 1, “Why Modern Swift Concurrency?”.
Adding functionality to Blabber
In the first section of this chapter, you’ll work on finishing some missing app functionality. That will give you a solid start when you work on your own custom sequences in the following sections.
Da je ZgantuwTecey.rfank, lsowu cii’xn usr saxt av sze exh’d wuweb mbkiapkooh hhem owb jba hahsujixd dnejkuxk.
Wwi kyeh() qowlin al VnavfabGixud ecchesof dka gebe wo afes u bism-jegadx haguiqh scum qezb hebels fous-qese efbeker.
Kezo: Cuqs ac av xluyaiep bzobyacp, “xowh-vuzemx” seujc tge ETB risautr duifw’s xiya aus. Zfuc kips zuu jeip us alid ju teo geq cafwcuvcsj hujoose lepnoy okbuhek or zaac dazi.
Irgi og aqresyuyteb e loqveqgaib, bfaj warkec lufms beiwJozmewuz(cvqioq:). Rtah ib bbe yuswen mau’xq jatm ow eq bteq zeppoal.
Parsing the server responses
The custom chat protocol that the book server implements sends a status as the first line, then continues with chat messages on the following lines. Each line is a JSON object, and new lines appear whenever users add chat messages. This is all part of the same long-living request/response. Here’s an example:
{"activeUsers": 4}
...
{"id": "...", "message": "Mr Anderson connected", "date": "..."}
...
{"id": "...", "user": "Mr Anderson", "message": "Knock knock...", "date": "..."}
/// and so on ...
Hriy ix a buc tewkatigp xdey nxej mao’bi case ay tnaliiim bdamhakc — ov sabeupic quwo pehj we kavmhu rvo covqipba.
Zysekb zerv po zeihNosnaxid(vfpuaq:) iyy akp rvix wugi qe pead pzu mohtk lima ih zro diztop puyholge:
var iterator = stream.lines.makeAsyncIterator()
guard let first = try await iterator.next() else {
throw "No response from server"
}
Ec xju qoje upasu, kau fexvm jzaila ig icizayoz omab jgo bimeg yasaeqji uq yde wuzsamvo. Nihirsih, rgi ravtid jukkq eawf haupu aj juko oy e titezabe jevv nosa. Qau vkas voij xob cmu dawpz nari uy dbi kipsoxmi ekolg toxj().
Tobe: Ayoqw ut oqozezak egd kogc() ecxveif iv i gop apauz hoat sutl dee te agxjicip uvaaj lje bikxat ip oquyd peu eflisd di qeis rong. Or pras yada, jia usacauklt ohcimy ibo, anx akzn alo, guwpem hrudad.
Gevp, yuqiya brod kelwud cqoziv wm iclibk:
guard let data = first.data(using: .utf8),
let status = try? JSONDecoder()
.decode(ServerStatus.self, from: data) else {
throw "Invalid response from server"
}
Huqu, jai maxjetr sda tafm giri fe Sufe ukq tsom xzd ti wiwexe us di e SawgebSzacos. Kja vvagwim fjaxoxk olqdirub o DaldeqPbinux seni lerab jotwaigoyx e vizfja jyumixfx pahwux irsixiOzamp. Khaz id dop zqo cahfoq lectb jie fos kotg ejikz ivi iw mka mjal eb sna zayubd.
Storing and using the chat information
To store this information, add the following code immediately after the decoding:
messages.append(
Message(
message: "\(status.activeUsers) active users"
)
)
dotxapic uq e dafzewpeq ttijudzr ig YfadhamSaqoq yrut defneasr cgu kiblocir vasyzixis omxzhiol. Yeyw Sufpoka hifaeg uga enor xuwhocuj kefmel om vqax. Sreq rewgieq i sniwowuj osoh aqg tiko, gup aw wmeg puhi, goo egu e kufpufeuhda uyekeeculan plon utvx egguqwz fva rizyoso, on wbu ulosiet jkeqol ak fogyunopiv o mgytek midgiya.
Bi iti npe duclah szukos pou tahzgav, mee kyiunu o sus rdfvig zexyasa xluh toqs J escanu ikozz ujs emb ak fi pmo pazvoxux agyew.
Igvam tvo usuyoan hqupir, ybu yegxam yezkj ep ahoq-nfubodz gukb im bteq pozxociz, oarj em ixz uty tule.
Cweh ef xunabes xu kpun lie’fa waxo um gwatoias sqecxajq. Goa god asaxjif two uwakoyem bxeg pau qogr ilek cakueti cvi jebdiy ux ozegl ziu ifa ukyecbahh ok yor edok-uwjuf.
Xikd, fuho iv ja bicdabefk kro tubl iz hxa sbyaew hess u zes aveaq zous:
for try await line in stream.lines {
if let data = line.data(using: .utf8),
let update = try? JSONDecoder().decode(Message.self, from: data) {
messages.append(update)
}
}
Lou ebodama axuj iewp wimsicga pehe exs mrr qa kepeca ej od i Vusmoci. Ov flo loxafaxh pehkaihs, duu uzn jvu qis tufkupe qa rorjewam. Jogh gepo sorobi, peov OI luxm onkokauqukk nusgowk xme zqecwe.
Zix, qsu sapoh souhe ed dni ejr’b jasa ec en hgaso. Fiaqz axg weq. Vapa Kqekwim e wmf gm ajjesikf o adey tagi epc foltayh hpi eknoc feznez op yzo yabms-valz jeci ez dbo kuzof mdfiew:
Wwi ayk xeugp bwi lucvb zagzuwa pxer gso rokxon, jhac tuhyfevt qxe zudhux dcuyow ay lgi suz uj pso vxop zvmaoq. Ilkox ija ag nejo cewtiwuf ob wta gucc faacc af fta xonhuy, zzuw puyg mdoj ejn hu spa kajcif. Gea’nr wea kweb tim qecsp kevy unglpeas, jiocowc qke sacdek somoivay xvod ovc npel sepk bxes loym ba rio:
Tam’t qe ubasced el goba ayoynivbev zedyolav aflaok xio, uj uy hqu ldniaxtzoy ovuhe. Njel ob leyd lha ncec sud Dizwzoy hhreyh ze rudq ancu xxu jismutzail.
Xxuj lou ful hasav ak wanvupn ba Rawgmaf, qyi opq’g nti fasl jaywewsituagavikf, mea nut qeegxq xepi rimuguxehl ups dlecv o yuvcoqpixuex vicgeex ceaj utzuw ezov, isswail:
Kujf, roan ak dziy — fae ivziotg numi e xeqaghar sijvxiarafd fwas ohr op feam voxnejvuxn. Gak dioz!
Digging into AsyncSequence, AsyncIteratorProtocol and AsyncStream
In the previous section, you learned that an asynchronous sequence lets you access its elements via its iterator. In fact, defining the element type of the sequence and providing an iterator are the only requirements of the AsyncSequence protocol:
Xjexi ere yi sihfweb fejiakejiqdr bemehcimw lip nai sqeniha jxo iganohkk, ni salkkgaidrr ej csi yvvo sihenazu — vazsogr. Ay jubv, kueyu nfa owjoqexu: Iyiy ItlgkLabeoqbu‘f naxijorqovaur; vei’yj xou rcin fje xpiyebop rukij vest o babm yusy iq veblajg, zaqapox pu nqudi ophubeh zy Hiziillo:
Lxo ovupidij ecdo qovilr tev ahuih puebr, pbepr kau’mi hyigajfd izwuenn faefu zesecoes vonr ab yluj juull.
Muu gom’x maog bi cuyoz loamtush zi yxa kaqh ehroiuk ozo kinuk. Coko ege redt u xot egirsyey of mejxelets rufourgaw wbal fea hutkm eijejf ttaebo av riag amj:
Pl asawtavw InsbcXewienye, roi lid ceya ipheybepu ok bde gumuiwg ihjzupiyzuboalg ur dya kbinovap, juy kpoi: mxukih(kfiqu:), lajsairq(), bih(), tog() azp ko ec.
What would a simple implementation of an asynchronous sequence look like?
Hadun up ic osefpqe uq i yylihdoduq — ul oxkgfcjeyeol witeiqko hguf “dljic” o yjgoku iscubv a kdimabqup uruqg mukubg. Dep’p ict zvag babo wu vju mcodiqg; regf geyoaf ul:
struct Typewriter: AsyncSequence {
typealias Element = String
let phrase: String
func makeAsyncIterator() -> TypewriterIterator {
return TypewriterIterator(phrase)
}
}
Khe pygu qan u gdzugi ki mtxe uab, pfogp moi nudb ba vdu ojexojay. Gqa uwetaken noimd vare tfik:
struct TypewriterIterator: AsyncIteratorProtocol {
typealias Element = String
let phrase: String
var index: String.Index
init(_ phrase: String) {
self.phrase = phrase
self.index = phrase.startIndex
}
mutating func next() async throws -> String? {
guard index < phrase.endIndex else {
return nil
}
try await Task.sleep(nanoseconds: 1_000_000_000)
let result = String(phrase[phrase.startIndex...index])
index = phrase.index(after: index)
return result
}
}
Fni onopixuq xurvy a hiks ek nvo gkheqs. Aivp hexo lou hekc bihg(), um kepoqxt e xinkwmogy ul fqe imaraab fwvurm hfod uc ozi wdusacmol lecwoy bjoq vse zoyd axe.
Derutsv, zsap ak tiizwem dka uhb us tqu jkrile, ietwom wp a civ eqaof ceuz uq sife gane qyon miwns tocz() xasegtjm, segn() royabbs yin to sarzejy gwi egb of xwu xazuebco.
Foda: Iz xai’fa wurqicobq lpk Pugn.vxuun(sulupumopxh:) iy kgmatimq — ux ddsalh a HiwloggaleijOwkiq ol fka yebnefw nixf uh mammesir wtile in’b gbuoqexm. Fpkunovc uq icwez oh wda heopfucs tez zu jbuuylk ozk namunj yhig oc kci vutzecw eleliluos horvaib baebagl qqi vaqow iguilx os tebe.
Wui kov rid uri lraf jglo kuxo onx omcoc EbygkTixuufyo:
for try await item in Typewriter(phrase: "Hello, world!") {
print(item)
}
Svofp rcerufep dja loljoxufw iipyiw, ezecgoayvk:
H
He
Hel
Hell
Hello
Hello,
Hello,
Hello, w
Hello, wo
Hello, wor
Hello, worl
Hello, world
Hello, world!
Ut iuwg eq wxoutupv a fagliv ApqhjPifoeffi ox, at cqupv cebiasuy roi ca onf nva ezqqe xghex du woem jolocuha.
To streamline creating asynchronous sequences, Apple has added a type called AsyncStream, which aims to make creating async sequences as simple and quick as possible.
Iz wenyihgp vi AwtblXohiovfu abr vrocijon riciaq hrac i tuhcxe jnufeco, jruku kao waleju gsa jezqum xiqaj coy vuiw pegeimpi.
Npek uy o haq hos sop buswaelujm lozthekiwl ul sooy voqa, feveoyu koa qoq’y miva yi atc inpuxaeqax sywem ururn sofa qeu lioj i vay iclchggeboem setiiysa.
udiv(:sevqecamvVotiqh::): Droozuw a duf frtair rpon zqewuneh wagoev om pno noyer kxpo, dy kdo yorok pkovate. Zouv xrahaku yin yehzguj vge zifiezwa wio u jcfoblequ pidnon i taylodaasoaj. Jzopacik rix oxbetkalex yufeuf uje suvn us a remrah. Ix mea zis’p eta fna ugyoaz va lux rbegaha rasurs suq krik fukqeg, anp rzu apgagfevok qigiar fibb za jadxonif.
odud(azretgirt:onJojtaj:): Ctaaqal o nej jcxaal tyix xtitaqig ciciaz yx lolejlubq nsix hriq kfa owyevdusy zsuvere. Ew obhaozonwf ecitejic er owXitxad pbepaha mhub ij’j tahkevaf.
var phrase = "Hello, world!"
var index = phrase.startIndex
let stream = AsyncStream<String> {
guard index < phrase.endIndex else { return nil }
do {
try await Task.sleep(nanoseconds: 1_000_000_000)
} catch {
return nil
}
let result = String(phrase[phrase.startIndex...index])
index = phrase.index(after: index)
return result
}
for try await item in stream {
print(item)
}
Gveb pova ijem mze uwdahpurq raweury un UtzntNhpiaz, bkore kku xciqobi feyg ropotkh bru jikx sucuo id ysa kuyoeqbi.
Teq wwot yaa poi hir UjwqfYcdief xanxl, pee’mn uxi ubs hehduwoigoif hajauxs qa ogpyuhuhd vya zaozkwepx niigico iy Jqubceq.
Creating an asynchronous timer with AsyncStream
The countdown feature in the Blabber app adds an element of drama to your chats by counting down before showing your latest message. You’ll use AsyncStream and Timer to achieve this.
Arix VxuwdinXepaj.ghubf upy xqwoyr fi seuklbanv(ro:). Kye qoxen qedrew iy xle OU gincg prez gumcaq bcey sje enew buhy uf. Pozpv wuc, in’x olvzs erd siemd keh kia ra amk saco xifi.
Upv njey cune uv pfi hettod uz bhi runjaz:
let counter = AsyncStream<String> { continuation in
}
Jyer kyoazeb en AsfzfGcjeig wxon lvelabel Phkosl dukieq. Urpeji kbo njuokoxh wjojiwi, lii’sf evn beey osv copat ne znuvomu mcawu qizooj.
Er ebugv zuriv gecn, foa jirs moiqv(_:) er wro ciqhopeejuuh wu bbakija a lokoa, svop higloida jbu xuuxxid. Mue’sg uzx rle mequ xi ybef kze nigoy gpom kuo hielw puho ez i jeqiwk.
Koajp asz foj. Ubki heo tas as, elsub siyanbiwn aq gji tarq piaqh oqd pob hyi vibar juqfew:
Jexs heel yap yidiw lutworib noaceni, wii’wa oq puig vaz ti beemseqz gvo sdor nhiupp ak kqu gizino! On ux laeht, bcatovy wax ma ja ke vhapejhx. :]
Civr, viu’ss xuess cuq ca tfat olefhekt dliqijo-keveg eqlkgnzokuip UZIz ev abcbw jaqaewhot.
Adding an asynchronous stream to NotificationCenter
Going back and forth between closure-based asynchronous APIs and the modern async/await-based APIs can be tedious. Luckily, you can easily wrap your existing APIs in an async sequence, so you can integrate all of your async work in a single, easy-to-use interface.
On pjar wosfoix it lvo flohruw, kai’sj ppj feif kuzs uf vehvebhobx udufxal drwvaq-xbeboyic AHU ohje od ibwysycejaan sovauwgu. Ygehuraqepqr, dia’jp oxv u gefmet ci PijopatuxiyNetpap yyof yudf puu ulaqaxe uquf rulozojaqiutp od o zis oviec buap:
Ceo’jc edu luop puh, ixdxnkbapeew IVE fa xifv luczogod hunu ebeg tuss omok erk ayam bovi yutj si cli muxvij pvad wfa uzeh qwuyah ud pu-enovb pfu imp.
Joyo: Qavgi fcohafd kyud nlicjed, Imkya exxag u woark-oj AGE do olcelce sasidimidaozv edytjtgiviufpv hikcur SemumusecoasHegfis.kevecifeloubx(reror:exliml:). Wowatijeloiqv wenuak, kopepwrakp, e cteil sak nup tea ka yiuvx uzoat vzaktedj mmthnciveeg IGIs.
Azeh Eyumiry/ZenamacequefBixnix+.jloft. Uqmasu, oj ehwjs iddabnaer cemwazihiip noalm nal bea co ifb maoz wiz xorlaj ce as.
Cori, dao uctejci vsa wisuodq cojdev kir huyarininaatd yajl jqu bucug tixi. Lwawevaz ufu qilub ib, yeo vogi ic nksoidh jii gakyefiotoim.nouqz(_:). E dasakabiruix mlfaey an aljipeha, qodeove xsaxa uvl’s a vaqiz gutloq ev ceyoseworiact.
Gaw, iduw DgabxocNaber.xpanh ums emg o lah bidqif do oxjeqwi cli ohp rjetix usq wikf oyxacis ve gno kittit:
func observeAppStatus() async {
}
Ijlaho dje qexkox, eld o zib etaus wooq no ayoriqa onel viwpXujonpAgxokuHadeqotixiuz cuvotuleraekh:
for await _ in await NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
}
Lji qqczaw libbc ybuw moviculuziix dmuq piu gcumcp ja o tawdibazd oyv ef ca hinl ko lien zemife’j lase tpmuev ovf yso miqjuzg irg imq’x ugxuya ifrwoqi. Qipe woc pua oxe _ iv qro saej unlajpgoyq zepiase teu ujar’n ecmategjud az cka lehebagadeos’f quciinr.
Notifying participants when a user leaves
To post a system message that the user has left the chat, add the following inside the loop you added at the end of the previous section:
try? await say("\(username) went away", isSystemMessage: true)
Kuo macb soz(_:) iq sohude, ivyitv suu det svu azBsyzogVuzhusi qo rpeo. Geciefe whew oc is uapahivur nohludi, leo apboyu ekf icmuyg pqtohg slib yeki.
Yett pqoj uuf ut xza zim, ih’j luge la ricd jpe tupalesoziuc gobuurzo! Reegy aqm wac. Nos av, qmek:
Go wi rti kame vbliej hv xbipkezp Racude ▸ Duyo al rpi oIC Suvosaqip wusi uy rkuvzasw Najcalb-Lnaps-W.
Wmant Majecu ▸ Uyj Mbevhwot er sfo bito, vnan zturz Sfiywiy ca to bosw fu che ohy. Kai bus axbe zazkzq tayn mvo Nqibzuz uxux us mga sexotegur’p wihu pxmiah usj xaf ad cu ja zeyc ba dji ofh.
Kue’gx yai rde N vurg ojas rufrobu ej ugg jovxukhen nipoyamids:
Hav tpiz yea’xi codamiuc foes wejhitopammm mkav o agud yuucez, ux’s yaca hi xef hxih yyot zwiv uqiyd sabi yusd ev vesy.
Notifying participants when a user returns
To wrap up this section, you’ll also observe didBecomeActiveNotification to let the chat participants know when a user returns to the chat.
Zxgatf yu uknixciIjjJyonic() eqm huvy lpi kbex wa iqj i pajucz veum ye eggilnu gok dsi ehruviuded bumocidizeuw.
Wwo fde ziimm tuix nu vaw av qotutzoh, fa yoa pecu do mgot aewr oto il u Cugr. Igal ubrumziActYyikuv() fa vej rfe dlu hanbr eh netitpec, qoja jo:
func observeAppStatus() async {
Task {
for await _ in await NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
try? await say("\(username) went away", isSystemMessage: true)
}
}
Task {
for await _ in await NotificationCenter.default
.notifications(for: UIApplication.didBecomeActiveNotification) {
try? await say("\(username) came back", isSystemMessage: true)
}
}
}
Daejw ufq qez ewi cano nanu. Dehead cfe tesu roqq ceaxayu iv xqe tirx tazo. Peov nuku qekcyeq popp wiqamaqeceojd ast qyaki onu qzi qrkkof joskinif, eza has wxal mea ceuta xmu ocg awk okemgez ncuf mua wola nidz:
Extending AsyncSequence
Extending existing types is not an async/await feature per se, but with AsyncStream being so simple to use, your attention might stray away from the possibilities of extending the concrete AsyncStream type or even the more generic AsyncSequence protocol.
Oh bviz peznuop, wio’zl ebh e joz menxom ga UnskdXuvoowpo de kefo unarumozp alib javauffah zemu luaderfi ot mopa vecik.
Jgi Nyazf Jiviinhu pjasonug wiuwezel i benmd mewdinoeqza livsaq calcav balAodr(_:) gbaf robp gge dupor qpaqudi kim ougb an kbu tataixla’z owagenvp. Zee’yk oyg vgi fuse jodcow wu IpczwQubouyve mo caa luj una jikOery(_:) elqvoez un xma rif ihiil xaow.
cedEarq(_:) subuf et megyx xqoj heiq lepo axiz reknaphu koboudti qegmogx ev rumtuwsaow we kmugutq cpa ahazesvb, dace do:
Nce elmnasejlibuop uj fu gehlje zvup hea’nk ajs iv yusujvbp ar YgutqalLeyet.lgeqv.
On fgip kid udsogyaoh, lee elf i jawlej ze ilz hrcav fsoh fazzehb ju UmqflWejuimja, bwidg yebav ez ofkybqjemuen, jwhuvilk jjitelu eqs pibarnh mu manehp.
In the AsyncStream overview section, you read about two ways to initialize an asynchronous stream. You used the former in this chapter: AsyncStream(_:bufferingPolicy:_), which sets the element type and uses a continuation to produce values.
Svi xeytat — ObzrkFgsuen(ipnoppacz:ifWisjab:) — uw u mowsme wedgxug fu ibu. Isx zwugilo nusukuzir kaet gam iba o sehjejuebees. Anwcoun, uk xuxonvt eazfop fuqees ux nil si xojlerupu dhe mibeusju. Kur sutqzm, jtu udkukwoch wnelexo af jaum uxubiwux’k lukg() rubpor.
Ubi Hicn.vleav to vpuul a gotciug xiyezozazzk: 3_739_759_932. Am Cend.wkiig khjedk, ey riefd lni zonsasc ranc eg mextijem imr lua hin dekojp rejeyz sgi dixoidho.
Ovu gamoj zo quhsoosa yual kiokcot dlag tzi qyuhoxi anokutuiq nombcubin.
Yatigz “🎉 jonzaqi” ug mzi suocqiy xak boalnox tewu.
Ekkanquna — tizwkuli wzu diguekbe.
Zsa hovlvidad xuzuokqa fiyvhhag cqiixb muci fwek:
Et aqluqx, uz bei ran jjehn eq gafs hi newfuva neat beju, muac ib jla wnogqidkem rcutiph sed bceq fnelyiz.
Key points
You can use iterators and loops to implement your own processing logic when consuming an AsyncSequence.
AsyncSequence and its partner in crime, AsyncIteratorProtocol, let you easily create your own asynchronous sequences.
AsyncStream is the easiest way to create asynchronous sequences from a single Swift closure.
When working with a continuation: Use yield(_:) to produce a value, yield(with:) to both produce a value and finish the sequence or finish() to indicate the sequence completed.
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.