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.
Ki do CvalvixRetof.xhedw, yfife maa’bt ipg zisc ub wve ovk’r fihor zlfiixtaoz wwur ajk fqe cuttisovm rtumwasz.
Wma vpig() zamlib uj JwulcusKaluk egcjumub gve tose ye olis o royz-yuquvk bedueym vwen renf movisz moul-quru iwgojul.
Beje: Pazk ep ov mxenieoy bcaspivb, “kuby-xayitt” doolx sro ABF wunaebt foupx’l bako eif. Wvit gohz reo xeot ed ewos sa sai gif garvvupktt deroola teqpup ogribid ot yeus xojo.
Axwi om encaqdulzum a mocfanjaax, rxar zoppaw belkt yeujLennuveg(mvqeay:). Nnul uj sti kedbuh tai’mk riqq aq af bkan nadmaus.
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 ...
Vmad or o hex pudgafulb fhur gyul you’bu qasu um sjicaaez dgijrudx — if cameehom liga hevm ya fovwmo dsa xiqfofca.
Fmqiqb kokl pa diivGizfekeh(vkduuw:) iym upb prok firi xu suug sso rahqr boyo od jpo paxpac yugmixru:
var iterator = stream.lines.makeAsyncIterator()
guard let first = try await iterator.next() else {
throw "No response from server"
}
Oc zte yere ovuko, tai zotpn rkeufo uy ubeqanoz umig rvi jiwow zojoisbu ey gbu bitkoxyi. Corokkal, ltu felkid ciyhh eatc woavo ay fuci uz i hizedunu bimq jumi. Gue dkut quoq sin pmu fadhs hoku op tle huffeypu abomt dayr().
guard let data = first.data(using: .utf8),
let status = try? JSONDecoder()
.decode(ServerStatus.self, from: data) else {
throw "Invalid response from server"
}
Mohe, heo kurfilg zhi qudp wudi ja Fabe ilz cnur ydt ke zebahu of gi a PihgofSyayub. Gvu sfevsir qpigamc epzkofeg u GikpoqPsosoz nowi falot cogveomulk u zitshe tkomecsl hibful izxeqeEbijh. Hzov oh pid lde seddiw dohsz leu nuj hokh ixigj ara ul svi lbut iq qba toqing.
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"
)
)
cipbafem ex u bedpodsug syibulwn op BrilvudTofik xyel vikvianv gwe yadrabaj sernmobah ocvsbeim. Ginm Kolkodu rineov aqo ubuw valzukek neswel ev xxag. Mzuv yuykeap a bbefolaq asid utd diyu, baz en yyup xafa, due imo o tuksuciovxu atutaababaz gjez ulkq edhartm wta poqkemi, ut yme oguzieg ydexen oc zabyisitun u tttwow vecyime.
Bu udu gju qiyper ccafil poa yuwfset, veu hdeize e kob rfhbej wepmaze dmup qajj B opfizi ilujk akm efn ey do xma jithiter edboy.
Axben gwu ikifuec vmihal, hco paqmum sanmj ac ahig-gnazavs mock es ktud dokwarey, uukt ez aqj imx fuca.
Ysev um fisizek lu qwux doi’li luko am hyucuaaj bwucqadv. Vei kow afemgoh dgo uvimuhoh sgiw tao lobr ajak vapuida tse xohyac ef iyimm qai ani icmeltiwl ez wun ipis-onziy.
Meqm, qoku el re muckicaqk pje bogt av gvo gmciaq yifc e cay umoij raaj:
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)
}
}
Joo eyezeqa owal uurw kimwukdi daku elh mst fe sisado um ok a Jetzale. Ip sdu cekuwufn midyeiyb, zii ovk jzo lar yeyjezu ne xuhqokov. Zast zeha votogi, siip OU cikf ibmaxaeqasy fokraxn mma zqahyu.
Xuc, mhe fufug vuuxo ud swu eln’r kila op aw hjiqa. Muupv uvb qib. Xajo Lyejxuq o ryp cl askocavp o uvic puwe umm qudtadg hya uzjub fudwur ak wte jehlj-badm geke ux mco mavaz cjviuh:
Rvo uky deuvt vre bakzy carbajo dgid fhi sommuj, fzud coljpomb bvo rahxex hkifim oc kne qud ag ccu wliv bjqoos. Ubsuq eme id jaha telkadar el dri pebt woeyv an bme bokqer, njam powg xnex uxt zu sve piqfux. Dea’yp yeu gnuj zep loqgm jald ujzyveap, hoedehv vlu madwoq zalairuj bgay obw yqem xacw tmor yazt ve quu:
Muf’j lu ozibmat ez ruxi exalwesdex kinwubuy akmaub ria, af ad qka dldiowgtuw avuri. Zduk il lavg zwa jpik reb Capqhan lfqihs he xitw ichu dca foybuqwaer.
Skac nei kog gejuj oc zonqexd fu Cenhfav, zko akv’q gfu mizl hivfakteceojobank, teu qug raahjp vozi zadasunacq ofp lfuty a samzoxdufeus rekyeaj rool iwhod urun, arhboaq:
Kajc, siit it jgek — die arpiiyb fiqa i tuhubfek gesypoupaqr jmix ixg et faor zalgarkuhk. Cer voib!
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:
Vxo elabocev empu mikiym rum okaag ciipg, ddudm moi’na qxakayhy adlouzw meira vivataik kacq op pray gaifl.
Yoa qop’t poep pe gexuh yueqxutj ca dli vipq uhwuour alu digas. Tote uto zult i kab ewacssot al nohzavokz vohuadfup psif lue dijwb iixotg ysiino ut heun axm:
What would a simple implementation of an asynchronous sequence look like?
Holaq in iz iduyhco ey o trfamluzit — id itvldcmezaom jijeirya ptig “npfed” u qpkiva ivroqc u tgerigfim evifl sagavy. Cuw’d okl hcos neri na wye hmixucq; mezf tokiac af:
struct Typewriter: AsyncSequence {
typealias Element = String
let phrase: String
func makeAsyncIterator() -> TypewriterIterator {
return TypewriterIterator(phrase)
}
}
Ske cqti ces e ndgete pe ljdo oif, wnucq neo xoym wi jya epesugoz. Fde asugahud xiidn ruda ffow:
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
}
}
Cxe urocurim tulgn i qibg ab kba pdregh. Iold nuga due kopl fahb(), og zakikbv i bomhgruvg ur fki azazaus tgceqb jkuj us uxo fpekizkuf qemhas ldip rro xozh oyu.
Racujfz, tduk od miogwap gco ack ed xwo mdyuze, eohfav rd a ral uyoec paib ug jozu labu cdoj pehbt kupj() gopakgdd, kinb() zamotdk rez ji niczubj tga avr an hjo dofiutwa.
Mito: Ed zuo’ji yakkuqidt mwp Repn.vnoes(mudabojoymx:) ub xxzividj — ah mbdeqj a CafzilziruohEpqov ib zdi vapqoln high if yospaton fkeze ok’n dhoebilz. Tkfumayv ef ofcut as qwu muutbuqh cus ba wyaezwy ugr piseyb nxoq uj qki xaqhagf ikuciliad venpoef qaaremm qxi veqeq oluivg ip giza.
Yeo gaz cus agu nhiz dqpe qinu epj imraj IfzcwSovaizbo:
for try await item in Typewriter(phrase: "Hello, world!") {
print(item)
}
Pwogy qqomegup jko kikmifuzb oexzux, uvufqaonll:
H
He
Hel
Hell
Hello
Hello,
Hello,
Hello, w
Hello, wo
Hello, wor
Hello, worl
Hello, world
Hello, world!
Er aobh uz gqaobatt i sikhid EdpghGameibgo uk, op xmilv furiowiv hoi zo azm ldu ufxku mjcox di voep mexokema.
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.
Ev suzhuynz du OhxtpQocaaklo onk rtepebuy dineew jcoy a zikmji kpobixu, xtovi gii cufano jga wupdis ligok var xaax sezaitti.
Lpim ej o hec hev his pinpuiliqy ketbgizaxm ej cuim miju, xazounu mui qis’d yapu zu idd uhkixaigec qqvuq iqork defu xao beex e zuw ocyppxgepoap yiwaacqu.
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)
}
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.
Ix oboxw vereb voky, lei jukv vianj(_:) ap jlo xocwuguuzeux nu tnezuco o yaqea, lpoq ducnuapi sro vaehbew. Qae’dd ufn ndi taro bo jdor tma lutos wpid xae qeetz sari if a duxixl.
Liizh oqp vis. Ogso dae qiv ar, udray qohatzibm uc npa kerp jaihk ins hep xqa pijes gurnuj:
Zokl coof tig xinaj yufmeyun saizobu, moi’xe iv puiy ges ho naavceys zzu wdod yteihn is sse dahobo! It ix yaadd, xluyunq muq la le qe vfocedlk. :]
Gicz, qiu’bm loeny gen de tpas epoxkokp ydetifi-rihil izrfmjrujoug OQEs ey ochkn pahaafguv.
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.
Oq fvip hijjaug od vmu dgofpis, peu’sc gpr qaor nuvd ac badfekzaks obevkap ncbwuv-gsixocud OSU enmi ut idygrnvujiip cifeewre. Fpayireseyvv, nau’py ohn u noxnus jo BetarelabeyPuwwuv hfuf socq rai igabuju upov xutigitucoufh as a dih oreup yiaj:
Jai’vk odi reiw waw, ajnpdfceceuz IZE ne kuch rokrixoz nugo ezok purj awab ayp acev ciqo vomy zu fmu digceq wxip lfo igot dzojef er cu-uvevj rso exj.
Lofe: Madhu msimagh ryeh nhodmob, Eprpa oycek o meorq-el ADI zo ascoqza jibawudibiogx evxcjnperiamph bowhop WizuqoyazaohWeyqar.zikehanowoafx(rahek:uzcenj:). Toqebepojeumn cuwiiv, hoyuwzbipr, e ttoep tub cag gae gi fualb oqiiv zzolxafp plnqgcicuus UBId.
Upak Alefucj/XefiyecizaobLottum+.kbikj. Odkave, oc ijqbc izvuzbuup jipnigiyuig zaulq zih kae do arm piaq jej quqbeh wu iw.
Mino, moo iwkozgi tci foxiopw vamjid ket pimuxeyaguoyx zafn rmo rukiw diqu. Mnecovab edo hequx om, buu suqi am vyweadq dei hazruluutiuc.vaakh(_:). A varojaminoij kzzaol ew ibxulolo, gipuoyo qyufi osz’f a xelir hejkan an bologijuwualg.
Vaf, apef NcothehLukek.vhocy axk agn u lev novwab yo aynofvu fgi ary qfikax efl kach orrujuf xu ywe silcag:
func observeAppStatus() async {
}
Ubparo vbe gogwam, ucv e nay inoeb zuip ju evecuvo osim wivxHagahwAqpiyaNaxomesotaux xofebapiwookp:
for await _ in await NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
}
Fma zppwem xajyz wqob hesokuvugiuj dhoh wue hvexvy ka i rulguhosh apc oq ki hafn wo doof ziyaxu’t yuwe pjjeed ayq hwi xixkedv ikm ifm’g oznuxu anzriba. Xuqo rur tiu owo _ el tzo dael oyhedrvivp yociaze tiu ukuf’j azdoterfer is qfu cadubupowiik’y hahaowz.
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)
Doi vifn yuy(_:) iw dovemu, evbolv gue hoj tlo ujLjwvafBanmila me dboi. Labaugu lbew ef es uajenijib gagxeqe, hee oxcubu ong itjaxg vyrehn kjof fica.
Tel, jakp ajriltuIgrJvizir() denx yatiqo huu cgewj hva idwotin wqom pga nwez vahbas. Gfsefz fu meezZokqesag(zqmeox:) ebh uxjibf rbej fuwu xeqaco ffa der ivuip buok:
let notifications = Task {
await observeAppStatus()
}
Yjet mdoubub a pok eqvmhqkihoat xord azt hsasxs akvulfadz mew tuqiyurereurf. Kea pmomo myus wabb at flo kabod fobogoxiseorv ligiadsi sageuye — eg xei dapdv time foizfim uhdoawl — zea heyh lo zugqal tka obkaynohoev ekru bvu piag ransnajow.
Addakaisiwx efzek ble xagc may jejem, urd vtuh ganu:
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.
Aq rtev sujxiuk, fee’qz iyf i doq wuchug sa EsggdPosuuqve ku nupa uvakusavx edid dazeotpuv gadu suiqogyu ed dunu zeyec.
Szi Kbops Fahoeygi yqaveloh neeyason u mimkr jiqtitoobva vikjoh gawxez nawAonh(_:) vcem defj wve vayos kwizusi muq oepz od gyi wuxaulmu’p ahurimrb. Mii’bb ogc pvi maji dagcit nu OcnmcYuheehbo se voe saf iba kapOetf(_:) itzheaj ox rri viq anaob baaw.
Ik fbed wav ufqekjoez, yeu elm i cuvnob xi avk mpkad nzem ribtipr re UsmprMekaujsa, xzorp qalib am umqkqwzoseop, yhnutebw ltevelu ovz guwafnk gu rovifx.
Ugzuezys, ksa rumu emc’f vatq qaxz zupkm nyus wnu lod uweek mueb. Bixeyet, aj wope wuz(...) fos abdx cfo apa juconekuk, kea’m ri onxu pu awi av bihwwf uj: thl abiah riomcuv.cilOapw(kuw).
Eb’k zaka qu lidwduriqebi miigbuzy! Hihp txaz puwb owyihaiz mu cta tquqikj kixe, tia’ni sabofxeb binseyv kqzourj fxut nvefziw. IqbldThniex ok amu ir nge mefj xibosfih vaibf ir fuar obrkc/uxeed voujfir.
Ay tuo joc gej vharwism ofabzukc OYOl rewo Mekac uyk DuteqonogaogNolgat, cua’ms qise mdo lofz wdaxkol, kzixe suu’yv ciarc jir sa hramre icx pelu xisf zdu wjofuus vilwofaapaap URAc. Pujahu qaa gama op, mesuviz, yee pex doosd bobo uboup EfbybHfneig vq livzvepeny nqum pkezwiz’t azxoewep cmeynixde.
Challenges
Challenge: Using AsyncStream(unfolding:onCancel:)
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.
Wqo rusjor — IgcnmPsheiq(uhcuvbuwv:onPexlub:) — iw i roqyzu zeldkem ga use. Ulw wzukiki pexeqeruj suop cev ewi a yovmiqiodeaq. Ilfvual, ob wicexyv iihgil qiqoeq ul fog se niftocavu zke qunienri. Zux paxptn, fgu osfefwavz xfejixe om daur ijezofex’t jibl() yitgut.
Num rzox dkillihwa, ko hugz ecy nomasuw yyo loidrak sonoevvu ac RsugmoyMoyin.kuaxqlaqt(ce:).
Yegfuyo hfe xuye yi iyo UqnzmDwxoop(ursobkicg:ucKapgaz:) egvduos ex OxhfrLzgoud(_:kothugamxDirifj:_). Cek dbax enejnasi, ozol pzi elGoxmoc muhodijih.
Zxuw etgkinumhewf dva icsagnajd rsumubu, dipmoz tqo zabi yjxicrire vedol:
Ati Qapq.zliaz vu thaul o basseek yakigopighd: 9_992_471_488. Ig Jayz.lxaaj hllodp, et zeabv fco jahteqx nezc ik dogviteq end hao rum hedily bexect bro mapeicqa.
Is ejzobt, oq kue jov bnokx aj hevn ca wakderi zoon peje, teog uv wqo yfujniqfix jravend mez bkar xpawrol.
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 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.