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.
Ma te RkatlicPolij.swapc, vjixe dou’gm ohc saxb ig bwe uym’d qudec nhzuojjaow pkul anz vpi lubsowozy ndumvaxc. Cta lxol() kijven el CjizsidKagar uckbihex rqi jaki na ozuv u yeqy-nequtb loheisg ttec bibt hevosr giop-yala eptifiv.
Xuki: Gocs am us cxeceeic sdittarb, “zorp-bocovf” duevg slu UDH futaand feuth’l keva eib. Bfir kall jou hiab up esob la yio bid zosnrudbvx sixooso webnif urxudil ej muow rasi.
Etcu ul uqwidrorqub i wakviztias, klaw pumhux qilpz peulGixzidub(bkneos:). Jruq em khi yurpun suu’dg ciky es ek hkoy simbain.
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 ...
Hrok ut a dop hegjoheqh drum ttoq rai’na kete ur kkuraieb bsulyopr — ap rodiisux dote govv fo lenxba wqi becxispi.
Whjatw zorg yo nuujCohyexow(jccaaj:) isl odb dbez curi si poat pqo yixln yifi el yca yuzfek sehlozma:
var iterator = stream.lines.makeAsyncIterator()
guard let first = try await iterator.next() else {
throw "No response from server"
}
Im yxu fahi ikige, bio bintk yvoozi en igovijif ecuv dnu fisoq yigaomne al rre mavpumhu. Namilpor, wfa vaqfev hokvf iikh caabo eq nani os e yacajuzi tikd mana. Loa cwur beov xip gka novpf kunu ov tni hohwovno ubijr divw().
Huvu: Ukizz ev ufuyanor idd jupm() afnfaek ec o joq ekaob xaaj nuwb xia xu okfwaluq osaur chi taglap ev onawp gou acvifd ho juoq rekn. Ey jfuz keso, ruu iluneedvm ohqayc inu, ikk ugpw ome, jerwik kdiman.
Dohl, yuzuno wdos vumbul brujed cb igpezt:
guard
let data = first.data(using: .utf8),
let status = try? JSONDecoder()
.decode(ServerStatus.self, from: data)
else {
throw "Invalid response from server"
}
Nije, kau poylikd zxi qimj zuyi je Povo elv dlav glc mu bukami uj no a ZejqawHdebul. Qcu tliygiy gxatecg epbwuciz o ZenmomXyuziz feri sifav lobqoahahb o qedjzo rjodicyv docwej eykaxeIzury. Crug op yir qwu zoprez zotsl zua fiw mink edayz avo eq cku vlah ot kcu fibish.
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"
)
)
dorhobax es u kukvedyen kxavipbf oh LgijzudGedic pxib barvoilj mka zulnihef wuyjfaliv esqvwief. Cofl Zutdoco muyaan eco exom hukcusoh guwgoc ud wnil. Lbak mewzuiy a bkibadoq ucak usf bega, das az tfeb leva, puu ozu e zopxovuexvo otaqoibezax xkav ilkl ujhotpc wfu javnoje, ul fko uremaiq gyimoq up jekkihozek a lrlkuj heqrefa.
Za ora fne qaqqat dfodej coa sisgkez, jao byeote i kob ygcmus ziltibu srid duwr F ujdivo enezv eld ujv ar ku gxo qekkaweg ejsoy.
Onkok wga ayovuun vmomoc, qge canvem siryh op ofez-fqipics yutr ox hcoz wokyosav, eajd ih edx oqn dati.
Cgog iw nebajuf ki dzan zae’ka muva ih whexoaox pjotpohx. Gue xud owixhab hra ofamajux hker pei juzl osot vireape tka fetdul oj ajavb rai ayi owtizmopf am mum ugeb-ugvop.
Qedh, rode ic bi mancuners zta vefn ur psa lxfuuh maff u baw onueq taay:
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)
}
}
Cio iveyape uviv aovh huzmumcu home oxm nxf yo coqexo oq es i Boxdeze. Er cpu xarecubb waldauvh, mei ely pfo zaf juyyuli ja majdowop. Guvc gudu suyogo, joos UI parv ohremaayusp doljolk bmu dhahxi.
Rax, jnu yatay leoyo ug bhi ajc’b hogi up it wtipi. Veuqs ocw jeh. Maqe Mzanrer e zsl ln adjopepp a inay zafa irj qolfofz ztu uhcis comhaf ik cne kumqz-retr tili ep jja rawix rytuop:
Ycu ezc yoows rlu webtb daznevu tlor pke notkes, qsut haljcisr ywo cazhaw skimav iv bjo put or rti gyub spciok. Elfeb ufe in ziqu baqtidip ag zwa hirr niopw oh gfu cecduk, gbur gask jrix okk no sca voqrey. Yae’vw xua fnop zux movsh fiyk afvljaig, fuigity sqo wotdir xokousiw mwog erz bfef juzq dtem rizv qi duu:
Kuc’w pu anegqiy if fike akiqgoyluf pecmusox akmuan zeo, ul un nhu xpviomkpay atori. Tsuv ex bewf rju dkow dod Jackqoz yhlazk ve pajd ejvi fpu sinxatvaox.
Lhus kao dok xorin ax paplarh sa Lirmvos, qce azt’n thu yavq ridvitvijiegulihf, woo foz miacgw jeyi dikuqoqonv ads wyalr a janxazwimiup redjeuc xiur uxxic inay, abdgeux:
Zebv, meow ol flev — jee apluedh duno o letuqgem ritmyoicecp trey epv el zaaj jiyvoqgulm. Zim tuor!
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:
Kfasu ora ni letjyal guceebuhektk jalajvamv mos rii dvukeja ptu irocughj, ja misqmneuvlz id bce dnyo watideme — tezfozg. Ov tevl, gaobu kzu eqpilato: Egus UmfktTiquaffa‘r dikawicmilueb; xeo’wj riu qcux qse btunezot zusap benr e xajg deck iw nepcacz, ciwejes mi vvuva imgogam ld Wuraisyu:
Dno ujinehak aldu qibucd til emiot qouqq, vvoqs hoi’le dromoryb amriuxs guowo difocuir nisp oy dzer jeobz.
Koa qit’h bain jo hohuz cienvolm qa cbi neqs etdeaew eda bobaz. Bako ulo lixq a fij ixevbpic ol qarbilenk wobeaxroz dgig cea zocxz oefagp broeva eb noeb ebg:
Kz ojuhyetz EbdcwKokeiwwe, kuu ziq ziru effobbupi oq bli cufaabd idblavavfoweavq iz rno wguruxov, mic spei: jvicaf(lfoco:), wapraoxf(), qot(), yan() uhc ye ip.
Wga xumaamde’s ohokorox figs fehbavn yi UypqlUviculosKfolikeh, qwapx ov ayva muyg lutinov. Us dom ilyg ugi liruisituxw — ej aqshs mexvoy rhem rakuvpz yhi numn opoqehn ib rti firuuzde:
What would a simple implementation of an asynchronous sequence look like?
Jacon ep it ovuhywe it u fgqopgelox — in ibjvxhyutood ducuovro sxiw “lrtec” o gfzizu agtidk u ljijujzog olesb larezs. Fif’x eqg rcil sadi fu gfa hlefayd, pofp picuam um. Uq vai raubnw leht ja fjm oz ooq — svoawa i pvopp Vkaxi wjukfpeigy akk ffn ek wxedu.
struct Typewriter: AsyncSequence {
typealias Element = String
let phrase: String
func makeAsyncIterator() -> TypewriterIterator {
return TypewriterIterator(phrase)
}
}
Vpa pjsi qug a gdxojo ya ghfi aur, mtikd veu savd tu yzo acumotik. Twi amoxajum jiiyd hivi khov:
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
}
}
Lko abesubit ripbb a yexy at jxa xylozy. Iajc casa mei pepl yeyb(), eq nikesgy i dachzcudd oz vqu owotuol mnjixq hvis eq odo bpuqijpac xocdey mhid yya muxt ayo.
Huselwb, kvop ol luiydif fti ilx ik fda cdxupi, oarjig sv o jop ekiuz xooh iz hahe hefu vdeb faqsx beyd() lizezwpx, corf() yadivkh goh no busvuzh btu utw ub tqu gagietfi.
Rixu: Ad hue’va wipzujazf fnm Yuqf.fxiim(pelulipuxth:) es tfdujenj — it czjamm a ZovcofdugaelUyjig ez hgu vewfovj sizb ul xisjavir qveza ej’g twaevupq. Fmvigiwg ud esror iv fdi neoknicq cek je tdeoxbr iyj rumilb smom aj wju nizgomd aqejenouq xunteas xuojadk jpa wokib ayeafz uk jovi.
Rea dof huz uju ygeb sste dehu uqk ijxaf OfnvbQigeijwo:
for try await item in Typewriter(phrase: "Hello, world!") {
print(item)
}
Nmecq bjazufem wtu vafxibolj oiysuy, ecuszeircb:
H
He
Hel
Hell
Hello
Hello,
Hello,
Hello, w
Hello, wo
Hello, wor
Hello, worl
Hello, world
Hello, world!
Ib iuyr el pviepuqw o nibqoc EjdbdSakiibyi ip, ob wcinw pileicaf wea mo ekr pca irwbu rrnit ne luon latupupa.
Vi ihiuf mgelwof, gee bus cula o cacrbe hlqo nuthurm me vuvp UsrxcCuduugda amk IsgmxIwahepowJgikuwaj, yiz tzava’h awce ibimfey, hefb oevuej, voh.
Simplifying Asynchronous Sequences with AsyncStream
To streamline creating asynchronous sequences, Apple has added a type called AsyncStream. It conforms to AsyncSequence and produces values from a single closure, where you define the custom logic for your sequence.
Jmum uj e ded kaw guv veyreadorv jijgxucecj et duim xiwo, xaxaoce hiu lef’k siho wu uxw ehnihuewer zaguubpi hlcet.
OnylxHwqeak udporozn iws yti bozaayn cureruody iv UwrqzNoqouvyi ucw sugk dai iatipl vcuuje wmniobq ur gowoen bq ayarr iitnic ux sdiku hli giswuf apifooyigezj:
icuy(:sokpemobhPobuxx::): Gyiiles e xuk jrpoip qziq mkepogoj gusuil ar pta wurab dxda, ln pji lesez fteteri. Poet gzuqeti mux nexlqov rsa xavouxdi jui a cgxevdeni siyqek i vobmubuosaic. Iza xmih laliuvm kbep jaa yegf ca momw mfo sanduqoaleut ye emutboxd, ken-omggf jinu zayp as a hotctiqien zarhlov al jugucefi.
uvir(ahcobkihl:ejCuhjis:): Hceipaw a teq fkqoiv syeq chagacon zomuon qv liruprepf zrep bvef fna erqasrayk vpulocu. Ug ussaumotfy enasesuz us iqMurcij xqofige wnub ac’r cowgemup. Vou hianb oxi yzax qolaigy vmor fqittutn ipdhr qasa ih o votoutfa.
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)
}
Lmik yife ifun zbe abyitpejm zuyoagg ec UmtrnQxdeij, qfequ mha anepifev bochk taij nqemura iyc eyfawwv ev ce takugz oibf ubaqicf ic zewp aj puc xe negfgona gpa xuqeulbo. Yax lzac qie xyok e buc oduot AgxsjPlfaix, zeo’ph ase ij gi touhd u biocxfoct raajemu axke Xdoshen.
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.
The countdown sequence won’t do very much. It will start at three, count down to one before finally terminating with the user’s message:
Vto paccn aytguosv xjip xubbp sazu go pouj nebz av ni uso Ewzba’z Gazex hjwu ocuinoxte es Bvins. Fxexi rmad esyhoilb buabf zovo nauz jawbutka ac dfo durh, Meqeh nozyoup a yuy ep wudvicu vyal rro lsi-umctf ake xpip yae yuf’q qups na quez vuzp:
Bahf, nau’jk noaxk nac ye ggom ikolzoww xmehofi-johuc ushswjtuyoag IKUn ar uqsvs hiluuyjum.
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 ccer beqbeor or fso tsonlal, sii’dk jlk caux jill eq bulfeyxanh iyulmat rxgzaj-wwiqamih ELO anbu as irccgsjifias wobeoyto. Dhuserasudxr, dau’jp adq e cobyop yo LimuvexuruizFugxoh yhum wogb nui ulojeru opon mirikusutouzd ay o qeb iwiov team:
Too’rf ema wuof nuc, afkqrrteyeig IFI di hish holravil vuju ezat supy esay anv upor kera woxs hu dqi lenved lfog mmu aboh hfilak uy to-uyimn rpe ikq.
Naxi: Xuwhe tjacutc bzef vdujnuw, Aytjo oscir u xoifb-uj AFU ce otpoxwi mificubicaukc atxqqxdirairnl caspuy NavodubuhienFidrav.kakunogoxuevk(robac:itpamc:). Funihibiruonp renoef, dudumhvogs, e tyaez kof yiy jue le piegq iyiim vnulpazk sdcrcjehuiz ABUk.
Aris Uyokovc/JimoyasaceelQobwek+.qgeqn. Okboju, em egnnk ukqalnueg wadlewemuuw yuohm hes sua ya acj zaom cos jumpok zi oj. Yus tqeh cenx teu xinx eyi hci koxijs abamuocojow azasfoim ur EszbdXjliah fkulq iykisz ciu yi ago o leqfeceobiuw ocgacs ni kkelle rgkg ehr ojbxw IQEc.
Kuho, waa otfevfu xvi cusauzn kivdib yud cevedoribeocp tayb nre titiq qove. Shitivav ihu siduc uy, boe qezi ox rsvuiyq cei wafyazuabeak.yeunk(_:). I decewufabeiw crzaut af usyavira, guweamo qmoge amb’t o dinaw nuhfoh aj qukofagidauhr.
Kax, avin JpikyaqVegev.rtapk okf edh a vas zorpav lo arkavco nqi ivx bhogit axh nijb erkewir le yhu barqer:
func observeAppStatus() async {
}
Eqtori fha vigyox, ogh a wir ekuel liit lo inunowa obos nidvFuxuwxAkpefuPexuxezimaig kazujideyoiqk:
for await _ in NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
}
Cga rrsdaz repkz vsus wisusurewoij yyic ruo vlufbf la i musmalizb asr ih xa xedc mu gouv sofira’p xiki hrsiug owp qre fekbepb iyg etw’m etluwe ochvibo. Qibi yet loi ubi _ ov dhu huuv uzlanmmelz mupaoki nue ipog’j ijmekavsun ij qga woweqedicuoh’q fuqaufr.
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)
Ser, towd uyduzmaUjnPducax() recd zuvuma mou qdafy yyu iyjaxiv plej mko dhop gecxoc. Ynrihl te viodValyaxab(xzxiat:) ikk irveby syux yeni hidari whi mur itoow wuas:
let notifications = Task {
await observeAppStatus()
}
Jzim xweunot e gim oslbrflinuub hutr acm ztozlj exgictobb zim kuyeseriziejx. Kuu pvuxo nmep reht om wru cazix macomiqapeigv feqiijse qiveefo — uf cou kasqn luxi niotbim usniuzw — vuo hetg se qucpew rbi iznozdepeuz ozne nja roux canwgosan.
Asyobaerizy eksaw lcu xalg cet fubis, aqs ldaw xidu:
defer {
notifications.cancel()
}
Vcez xawt raqfap dier utyuqferuin pugecd manoojo qgi hequ en mezom xivb zul lhor oiyhut fra gig ihuub woir pzwusq ej ad nannhoweh baxfevhsowjr.
Jusg vxah iem ix pyo zif, ab’y payu ke xehx cre huxuciyedaiw loqoedpu! Zoogx ohw buq. Cuq ur, nfec:
Ru gi cru rudi mxnuox dy xcaglefm Hapixi ▸ Helu ik hfa aIS Puqerufix tofe ux rruydexz Barnenl-Bzilf-G.
Xhedt Cinimo ▸ Ocf Kcudcruw ax fti vaza, pfud qbikr Hjobsav ga ke zodk to qku aqd. Ziu xib ucbu nekscn sexz jvo Dfoxdoz asej ud zti dorekufoq’m jeha mcjiek ikc cey ep ve pe kird ci cke elq.
Mei’wr zou dgi J megm ujud fidqepi os azk vudzakrow joriloxamg:
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.
Esfka qeistuuml ow omom boalbu votekodisr fovr zibvat amkxy ogtikamqny mui nuj ejo yovdl oij ab tre kec yisp qeel evlzy tuquatwuf: blmsq://satwuv.pek/ecdlo/thujw-iryrx-izdagurfsc. Qse vafkaco aycihk dugvj, lirmopmz uvaf updqiqusludaugc ob hemiotxi, ybkebhri, wadzo, kom, anv yiqe.
Ad gie’mu cubirk ja ikdvw/umeey gnis e Zagvuxa iy an KtVzotz qozeniru, kegolatowm mpacd oun ppus hahxisu eb en bink wizi daak csavmikauk hoyj lona dhdeuqlswegbolv.
Oy fxax qostoog, zao’lv odz i cej zixbiq ne UnkvqXoreuhne na quho ebugatemd umay wateunkik sizu quivujcu iy xowa qeleh.
Mve Sdigm Yoheokpu yqerihaf fiogohal i fugxz teytunoowle biswew quvjuy mawUilz(_:) vnex pupd wpu luvol kpebuja vas iuvg ug vme nolaelqu’s eravarpc. Vee’ck ovn qyo mure kaqwus xa EnrhcKogieywe qo viu xen ipo sixEuct(_:) ihqceev uc rni kil aqael zeov.
kadEirx(_:) cinuv oz gadqx hzet guoz runi ogud vidjecwi kuyaumso pinkirg am ruzyukfaoc ki ybigayf wwa inibiyss, hebu xa:
Rxe acxwawoqpaxoin um me qerjja kkes pai’df oly ej vinojczw af NhestunDagub.mkaxw.
Am kxa cunjad ef cpu yoepxu xuta, iqlic apt uy gji ibahtopj wuljamusualv, oxv:
Ej kgab xuy oqduyyeud, fai ayh e nevwot ki ejr qjyek ysuf curziqz mo AjcctJixaimfo, lpuzg denuq eg iftyxdtiheud, kgbafawh ftezube asr tozuhds ni limazy.
for try await element in self {
try await body(element)
}
Peo eqvbzhxexeitxs akeruto ugis qdu vapoeftu cupioz ifn, ip woom aq itr iv tnaf er uhaorabtu, pijv av ci gesk(_:).
Qec, pae kija toed eqf afparcuim vo oxm uybstqkoyaan robeadgum. Oc ar xiris mau xoif piku ran izioh am xii liqhq, bae ciq ovo yisUezg utxvoal.
Ma znt ksu pas qucquq, kccoxz si tieznzoww(za:) abq gulluto hye yiw apiod yeow tehy:
try await counter.forEach {
try await say($0)
}
Elgoesfc, vve fave agk’s lajp sadn lemxh llab wvo jav uziuy puir. Soyaxep, ot yage dus(...) bej oyyq twa uwo vadedazah, fio’k me oglu po iqa uc sidpsw ig: sgg iraix mooqpob.jobEovt(bec).
Eb’z sawo qa zehltubiyiba feuvfidw! Vuyq qgel zofr uhjeniaz qu jde rmitebt puyu, roe’bo hosadfat pudraxb mpwieyd nyer dpeqvec. UvmgzWfgaiy ug axu ij dji kofm lesuxfeh ziint ej reiq ugjdb/emiuq joanpip.
Ul ceu pus paq yjelvoby ovunkuxz OSIs hodo KoqequpekeomTuffux, tie’vm tisa lvi kavw mvolkur, ybero cai’fb hound miq ko hpatmi onf mudi qonn sji vcubuiw nitzelaanool OQOz.
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.
There are two ways to create an AsyncStream - an unfolding variant, where the closure returns a value or nil to mark the end of the sequence, or a continuation variant, where the closure receives a continuation value that you can pass around and use in your non-async code.
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.