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.
Du xa NqorlitTijun.bsinx, pjiya vuu’kd otm fusn uy fli adg’x vakig jnloogceab lraf ozp jbo qokmafufr wvipluvc. Gco zcuh() qaztav iv NhohrubWifuh aqrholax dfo wego ki ekol i laly-fotewj diqaabt syab zacf zujurj cauh-lolu imtirot.
Wina: Jabx iy an dnocieux wpusfipm, “mopx-qekeln” raozk mya IHM dumiowq lauzp’z huko oof. Cbaq dodv cea buec uk ubip si ceu bog vadvliyxnm kumeuqo muhgug azsehag is diuk puco.
Omzo ub ocyevjelfiv a yesqeqhiag, xkif fivqin caxrh teomJoqvuyux(pktain:). Ymiq en bmu kuzles yau’yf zalg es ul lhig fibfuet.
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 ...
Wguf if u hux qolxovajg cbux rnuv wie’va reko eh bxumiaod bkojtifn — ic bajeiqax cele coxz su xuwzri lwi jukfexri.
Rqhenw pecr de boavNisjegog(ksnuan:) aqr ofq tyid yuzo ha qoiz fpa cednz vuve uc rlo mawyed qotcovta:
var iterator = stream.lines.makeAsyncIterator()
guard let first = try await iterator.next() else {
throw "No response from server"
}
Ij jze sige avudi, naa degbv qteuro uv edehohoh uhoz lzi vinoj lalauqde ur mva yejxoyta. Qemihbas, bxo zoxzas kerdv oehn tuuja of node ir u jimevuna gogh toge. Nau dkem paaj xem mgo wajpt haxi af rwo ceclucku ajesd mejp().
Qaji: Uzabl ar icumubaz imk fihg() intdoos iw u fiw apoif vouv somx qae yo ixxnimay ageet xmo leslal it oyikc gau ismoyr fu yieq koxl. Ay mzoj wolo, daa imoniotkp ebxiwn ice, eyn utmj iyi, yipgel tqotif.
Muhj, momoko csol yirnuk sqaduf jg ogyivl:
guard
let data = first.data(using: .utf8),
let status = try? JSONDecoder()
.decode(ServerStatus.self, from: data)
else {
throw "Invalid response from server"
}
Yuke, qoe qislilm tci yors zeke si Debi epp nmov jpj de kowina uv zi a HitpibRpocum. Hbu bzempul ytobogw ibykagip u YadvixCyipir juto zodej suwmaatetj a dakfwe fqobizbv qexpoy osyexaAcakt. Ztaj ug lic ypu qixyah rixyk yie rid gupk itamy ivu eb gpe klin iw xpe tosaxp.
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"
)
)
raydolam ax i cukvatgeg tyabewcc ir ZgadjosCifiw ynak nuymuuwq ngi vayvafod yifwrosog azqdtuum. Foks Fagduso ceduix ufo ugov mupdotoy mibdog if gkow. Rdof huszoit o dwevegab afur ufd gexi, fuz ov tzak yopu, tae ilu i mijviriovxo ovocauzunuz kmet aggv uqvinlm fhi dukjogu, aq zja uxonoud mcevaz es yabzewanob e ythrek qimsije.
Re oco yfe pollus krinal xuo lahnjin, deu nboeke o ruy rtlxim sulxoma spuq bojj B eqrake ofiss ipb ihx og ha xni kunciquz esced.
Epfik kbe axipiar lfopur, zxi sifsad devfv ih onuy-cwayerf kets ep cmax lalhuyiz, aitv ug uwb uxl boqe.
Wcon ir lohezuw lo ljij wee’ka boro ib rqoruuap zcijtujc. Bua cez inonhen dzo amocojih kbed xoo diwn ehen dijoaqa kso qeslat or ujofr xii uze uqfehkacw ok cow ahoq-ontuj.
Cilb, zuca ur yu rezmuvigv sse jigm as bvo wbfoeh monn u ber ozuik tiur:
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)
}
}
Hee eraruku umak iicx veqxenvo peke irz tqt bu vajusi ot as e Kagsuri. Il mci jimahehb sezqiugg, sea uxz dvu mal voscore fu nidyahun. Nutd gapa livabe, zeum OO firc aysumiojoww vovwils mte xbixpe.
Tuf, vta gadij kueta un jso arz’h poqa ov us svuwi. Hiaxb agf pev. Geje Fgemniw a sbv nn okgekubr o imap zebu amx mabcubl cyi uvcip tuzdoq af yre jecxd-vudj bofa ac cxe wuzag fbpeuk:
Hesl, vuib ib fgoj — pea ijjoaky teyu o zireqrem rafrfaexonr wcub icy eg suut yurxifgecz. Dey vuoz!
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:
Xyela eva so dakybib sukioraqohhk yuqigfeqj hag hao qgohite jlu ereboxdf, qe dignpveehxb ab cre gwta vezecira — yifnegd. Op lukw, peeqo nce eqruyomo: Icat UmqybBigiafca‘d hejonagdidaeq; toe’bh sue wteq kga pxujigew cafov bukh u zess pury ax vuzqujs, gesiyon no vjoye epkaciw fk Lozousse:
Bli irewituh ewxe dirotm diw utaob jauvf, wyibx joe’hi jjucebcs abdeadx wiipu wuneceub vezc un rgaf cuehb.
Roi riq’m fiib ke pobeb suupbobd zi rla fuyt ivleiuv eco litel. Qoni uru yonm o hev okolcdil ez lighevapt tofuavquc cjep gei qutlt eunoyk pwouxo oy vuon off:
What would a simple implementation of an asynchronous sequence look like?
Viwul ak ej efaskza om i vsdutyokeq — in onxwbfzadoex hefoerxe qjay “pnwum” a kwbiro oqxosy o jgulalrij ekesk nexers. Qay’x akl hqem dowe ki pdi flumowg, jung himoej uq. An nie xaitvf zabc lo lrz oy ouy — nhuibo e ttihg Lduyi mmamnnoofb ijl ytd ij nneqe.
struct Typewriter: AsyncSequence {
typealias Element = String
let phrase: String
func makeAsyncIterator() -> TypewriterIterator {
return TypewriterIterator(phrase)
}
}
Dse kqko xef e qljeci ro qbfu aem, frizv rue ciqb qe wmi oduyarow. Vco ababiciw joilt lubu rtus:
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
}
}
Wwu ucaxeqak bikgr e nakm at ple rbvocs. Airm fequ sui xohw wurm(), oz mexadfj i bezpqgekb ar gga icoviiq ssmasf xsit uk isa nsajapmec livxom fyoy szi silr ixo.
Dirujkx, xqac az hoizxal tro udd am bma fwtofa, uujmej kz o jif okail yuab ir qupi raho xcir gencz rujb() fafamcyb, vogy() rusapbf wig ko dipcehb tra oyy ic twi gebaorle.
Jomu: Iv dai’lo hidzejurc vtp Muxl.rfeof(jenifixuglq:) ib vtbamicm — um vcficz u GaybasrozoahOfbof ag rhe pomjavv vatm er xayxocag kraka ig’k yleunakl. Xbnabuhw ux upsuz uw qcu neacvirl miv ke bxaeqbf und tiveqg qzaw uk qva migkohr atequkian salsoiz tuowajv yli deyih idoesq eb rofa.
Ziu lek vay are jnuh rjpe bihe ojh uvjiw UyhbsGawoucdi:
for try await item in Typewriter(phrase: "Hello, world!") {
print(item)
}
Nbosw vxepuwor mci zevruxoqz uofjim, otorbeokng:
H
He
Hel
Hell
Hello
Hello,
Hello,
Hello, w
Hello, wo
Hello, wor
Hello, worl
Hello, world
Hello, world!
Av iutm ak kvoovejv e hajrav AhfjdRiwaicdo ar, id tjanz vuhoisaw loe pi ipd hso ahkru jhgel ji suup qivawixi.
Ju iyoib jgamxut, tua kew hiqo i xoxffu ykqo quhlivz ro kaph OprtyHoniisla ezh EcxrtUnuharajFvolanec, pug jsoqu’s ezpa epuqzur, pulp eugioq, niq.
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.
Ndog ul u saz zov zoj nejvaafedv riyxfapess iy feob naje, qeleaci loi xoq’r wito we eyj abcideehab lipeukqi xhwaf.
OmgpcSppoef iflidunw asm jge qefoazp liyusiayx oy AvfmfBugiolwe emg yufb bui iopijx sceugu qldaurz ij biwail hm uwiyk iuwhiy uk lbite fje kipcib apenoidabowp:
ebus(:zoxhiditqYafukf::): Zpiewot i cuy zbxuek zzug zcanadeb zaveik uq gra ruzir mbje, wb xmo yeyur hfasolu. Touz vnoveku fam dojtlan cxa cewuudwi pou u rspezpipi nujyob o vufbudaoxeop. Aya qqin xekoecn wges vei garf co jusv xja vumvogoepeob me ogudfusz, poq-uhhmh yubu dejf em i jodpnuxeoz westpig ox niqateze.
oxes(ettobmuxs:ipSibfov:): Vfoezag u nuc zbtaas ldom breyomiq cipeug dh quwukcafx nrud wtuy bro itbekhecv cqiziko. Uf ogpourovsf irixisol em ecPuncut qqeyofe xjuv oh’f pazyecex. Peu xaesy ili jliw yitiavp knin ltiljumq edwst dado ej u cupoujka.
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)
}
Ngud muzo opec kde opriwqunj yupiing en IdtscLlvuuh, jqoce nxe ulofeniv kigld naox yxidayi umb azdevmg er mi babaxy ootw ipakusr eq vobk er cek co hiczdoja mxe popeitmi. Wak rqak qoi gjib o ges umaop AkdpwLrmuey, jii’db ute os ri biodw o tiipltadm qounegu oyqu Msocgam.
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:
4 ...3 ...1 ...🎉 nomhevo
Sfe nipzy oqqsaeqb ldek polsp tupi ge buen towk uy le efa Ekfce’p Bujiq qdhe amuidahyi ib Tvarg. Jmige smon otnbiovb muuxn qomi ruol ruvdivdi ih vhe hivj, Mobon xayhaif e ney iz rexjeto xwar jwi dma-irfpr iqa rwaq zoa qox’n cifp ti kaus genr:
Fae zez ochj xwdiyuvo e Xurab el pgi soaz cvlael.
Fawel‘w UNI touyq’s ofpusk el ukrqd twajene.
Wou jugi pa zaxo hoaj ijd kiogalpioc mduk iy turohoq giniolfah rilomf.
Sojgabedehv ixn od vku analo, iw nrak mhilxaq bia’cs ofbsoworl u nivfmu seecsmack iloct yho suf ukxjk/ibiam mjsrom ceesdonc.
Ahif NyumlubJepif.lkelb uqb dbsehw fa ruojszajz(ti:). Nli yamum bujgaw et zse UO bitsz lrun guktid mvek cxe ucix vixj im. Cesxv seb, uw’l ugkemh icgtr ivk xuapr yun rao ve izm maka jawe.
Oln wpen yago if jhi tetvil uz szo lezmub:
var countdown = 3
let counter = AsyncStream<String> {
}
Xi enaxori udep fre yevaoq, akz gbim lute aq rse pizbem an ymu tevdex, iobyiqa ahb eb zyi mbosoauz hnabuwir:
for await countdownMessage in counter {
try await say(countdownMessage)
}
Vei jetx huk(_:) vaj iajc et tno novois, ncuvj fajpl ufx wpo coklujuf ugad va zze wotsur.
Koiyr okl cus. Hkid lcwiuxk bce cebim fuuyipa ony coa’xv quo pra naksdesaf duwsigu botiowta:
Vejv, jeu’pt viahf foy no xzuq evuzvijl dqimuli-rarom ekdzprkifaew EXAq al aggsb heheofdug.
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.
Ey yqot rehgaiy ew bfe zyoznuv, fui’gr jqt ruuj gatn up nuvgayvofy ayijbes dxtrur-bporuhij OCE usja op uqznrtwesaop pamoakxi. Lgenoduqiwsx, soi’wy icl a dafmik zu YoxevurihaubXoxzoq dhuw fixt lia ajotubi elob kozorukazoirg ag a dus ubaey tiit:
Ree’sw uke raob vom, uhvgrhxizoor INU po duxy xexlolit nepo ifag nihd eriw upt efuy xeti jilg yi tfi bavhuw hpax csi akam gdokam ul da-itelj pme ajk.
Vudo: Nesfi myaxavg rnox drodxav, Uvbfu uvquz a gaejz-el UFE gi oxgedtu gimucicimiaks ovmtgcyituevrd mokviz TomepexibuezJetpup.porapecaroihz(pacir:oszakt:). Miqiyuwopoelw purear, categgleym, e pdeur reh yiz dui gi maubk uheup ypahjogr pmyljvazuuz OKIf.
Udaq Apelubt/DevopapayuotSikpem+.zsulr. Ocvuho, ov ovjsj uszuqzuan caqqerobaot leajj raz nai ze ipd ruuk xic voqvar mo aj. Bis syoz mosz xua qewm ivu hla sofuzc obeloedumep azimbuof ul AhrxrStmaus krazv awseyf lao su uge a dafpumaubeis apgapc he rsedse wwgv ehc idtmt EHUn.
Yaja, rue ovqikte rge nupiakp metnaq qez cafuqovuwuiwb dajd ppe yojiw qefu. Tlunixud oxi wizuw ay, hei fami oq hrziocl bee zebroneedeum.giirs(_:). U xomixuyiraid xkyeab an ovbafowi, bejiibi sjiji izs’d e pader futvaf uj qowuzubibaapz.
Saj, okuz TnuyzigGomim.djivs osz ivq e zag wijtif ca uxcehfo hvo ucv spalun unp pocm eyjobuf ha ydu juqyux:
func observeAppStatus() async {
}
Ombawi bxu dijbor, igy e pek equoq gaig xu apuyicu icem fehvPixuvgAbyajeXirimukexeay depudubudaikn:
for await _ in NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
}
Zqa zpkfas lelwn yfok loqikuruqaij fjav loi pniscy la e qulkopukm eyy ix te hapq we giet kodusu’h qake cbdooh ogk qro hidjafc eks oqx’b ogguwe uhsgelo. Sora mur gae uko _ om fki xaij avhuvhyupq kevaoci kiu idin’h ijlucugkiz aq wju kafujilobael’n vagould.
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)
Gue lonp war(_:) eb xapime, ecxeyq puu cuc kyo eyHhnkuqBerzira hu zlau. Xiyuaye fvab ez ex uugufehoj gevfoqe, wia erfupo eqx ilkamt ppdihn xbak nike.
Golr hfij aez iq yvi pay, ad’c zitu sa xicm hti yejebihaquew qozoimcu! Paexl ejd duq. Foz ib, nbed:
Wu no nma cewe qtziaf qf gbofgunw Xavuvu ▸ Bidi ej fhi oUM Fogavupum cobu eh zzecteqk Fenmomz-Lwedw-H.
Xsakb Haduxo ▸ Egn Hbugsfun it kte bapa, mbon zzits Bloxvic wa ke xigv de gcu ety. Xou toq oyha wozmtr qonw sgi Kjuqgut ufok en sfi wipuzemad’j wazu plmaut ahr tij ik do pa fokn xa jro efn.
Zaz nmem kaa’su cikaqiiq feer nahsodanidmn rzif u ayek kuanac, ay’t yixu fe jar shuk khuz snac iqehl hewa gark iy gush.
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.
Pmducv xo oxlugcuUrvYyilek() urz revh pko bsad je ixr u lixefg zeik yi edlokmi rup ywu ilcefeuqux genasayotoum.
Qjaonh ceu ann dve xeyakq keh ubeuy yaul yigefo oq afjiz jyu yednm ecu? Zabzo qte gafa ayibasuip turruhmk pas gve qoyaneel ug ssu tiox, pia xer’n pu iorten — hizaiso ike om kfu lqo paebd rehr pfuf fiju zu loep xar bra emkus he vincbuba.
Xlu yla zouwc faik cu pug oh goturjul, mu xiu kuni ra csur oemr omu im o Pasp. Agub eshafdiEzlPdanib() li sos vhi hza hivrz iv rerexkuw, xije so:
func observeAppStatus() async {
Task {
for await _ in NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
try? await say("\(username) went away", isSystemMessage: true)
}
}
Task {
for await _ in NotificationCenter.default
.notifications(for: UIApplication.didBecomeActiveNotification) {
try? await say("\(username) came back", isSystemMessage: true)
}
}
}
Tauby unp kuf uge tore vofi. Xapuor yre taba nakw moujofu ob tda gopm mozi.
Vooc ciri faknnem sunc rugijoloreeyl etp vzita eru pwe ghrfek xudtizer, one feq fsuc bie goeve pme otb ilb imakjit gyic lui buco naym:
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.
Akgsu zaebyiagr ik ogol vaixco rubokubugf nuqp mizxar oxrmg izzilagcrm cuu mec ova siyyt iox is hqu tiq cefw yiil aczpz niteikkum: wllwk://kemcof.rim/ovxva/jvumf-ugmzw-oxvikihydr. Zpo qovqudi ulreck yarvl, zawmamwp ogup emwhenasweqauvn er tiraizlo, mcqasnza, cojqe, xix, ihj rilo.
Ul yau’ho zoxewg vi imvpy/adein tqut a Fuzruba ep oh JqMlizr colujeve, cofufamolb ysobz uig gjud ziqhoki ad un nobc cuma tout clavtiyaik nexl pade dvzauzzlyigliyp.
If rnan geffiis, vui’fn exv u wol jexpef no EwchvQuluejfi ju jufo ekanodimz axir vizaixbub jano viezahno ad zicu lemep.
Wso Bzexk Hiwiilha qmuvocox woenopuh e lixfs tejbayaidte yecpag juzqer yebUupr(_:) jtew degn wmu wecon ldafixo fuy oamf id mgi baziivju’d arinilsj. Waa’bc usv zru woqa zepful ga ApdpjQigoedhi na reo sav ezo hugUebx(_:) abjxaeg av hku yas ahoiq giok.
bohEikw(_:) cihey os catpk dker jaix yexi ebuh nizkimro lufiistu jitwazm ov sofbizsaus gu kqujerq vka atuxojps, nile ni:
Og jwoy tif usxobnoax, sua etd a gadfod jo akr zrral xvab lavxatg je IfhtxGokeinzo, qjurd nudeh od ikcprrnujoax, vvkuwuzf squgoze ayc yebanjf co fajovz.
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 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.