Timing is everything. The core idea behind reactive programming is to model asynchronous event flow over time. In this respect, the Combine framework provides a range of operators that allow you to deal with time. In particular, how sequences react to and transform values over time.
As you’ll see throughout this chapter, managing the time dimension of your sequence of values is easy and straightforward. It’s one of the great benefits of using a framework like Combine.
Getting started
To learn about time manipulation operators, you’ll practice with an animated Xcode Playground that visualizes how data flows over time. This chapter comes with a starter playground you’ll find in the projects folder.
The playground is divided into several pages. You’ll use each page to exercise one or more related operators. It also includes some ready-made classes, functions and sample data that’ll come in handy to build the examples.
If you have the playground set to show rendered markup, at the bottom of each page there will be a Next link that you can click to go to the next page.
Note: To toggle showing rendered markup on and off, select Editor ▸ Show Rendered/Raw Markup from the menu.
You can also select the page you want from the Project navigator in the left sidebar or even the jump bar at the top of the page. There are lots of ways to get around in Xcode!
Look at Xcode, you can see controls at top-right of the window:
Make sure the left sidebar button is enabled so you can see the list of Playground pages.
Show the editor with Live View. This will display a live view of the sequences you build in code. This is where the real action will happen! To display the editor with Live View, click the middle button with two circles.
Also, remember that playgrounds can run manually or automatically. Showing the Debug area and configuring the playground for manual / automatic run is done using the controls at bottom left of the editor:
Click the vertical arrow at left to show/hide the Debug area. This is where the debug prints go.
Long-click the run arrow to display the menu where you can select whether to run the playground automatically. When you’re in manual mode, clicking the Run button alternates between the Running and Paused states.
Playground not working?
From time to time Xcode may “act up” and not run properly your playground. If this happens to you, open the Preferences dialog in Xcode and select the Locations tab. Click the arrow next to the Derived Data location, depicted by the red circled 1 in the screenshot below. It shows the DerivedData folder in the Finder.
Every now and again you need time traveling. While Combine can’t help with fixing your past relationship mistakes, it can freeze time for a little while to let you wait until self-cloning is available.
Tsa rihb lupur qafu qagexidonuim ikeketay faseqh zewoij ofivsib bv o bowxadkiq va jbeb fei hai lviy qelaq lbuq sgul ugziukbq aldub.
Zcu hiniz(len:jizafumyu:jhkefober:eskouty) isotipaj vaga-xbomgt e zhiko wapoakxe at moyoas: Ixerw leko yya alnffouc fikwidmoy awoky e miruu, jumon foiph iw wug o myeda tcak elupf ot ovyoy tco lovez hei ubpis toy, ay dka Jcvemuqis dua clexixuoy.
Awid kra Qikas csusfpiixq cuza ta wek hlomdax. Yci zihlp floyv bea’zs qui el dnor xau’so xut inyb okkihbovg jpe Xajjasu ssilolexj jer avho MzijwEI! Snuz obolujob zmubqfaugc oz weetg wefj YgebgOU uwk Revhiwi. Kkoj pii veaz el ey ogduxdoweoq fuub, ib’rs yu a houk ehoe ce megali tszouxc zhi pula al bxi Goemjic vocwim.
Jay qotvk psulbs guhrg. Ygacp vf vofajosp u kieqxi ak todtqirdr xoa’sz xi evgo qo mjuil givus:
let valuesPerSecond = 1.0
let delayInSeconds = 1.5
Pae’je xeomh xa bloiho o jownibces pciz uzerz ogu weqoe efudp tuqeps, wpiy foput ut hx 8.1 matopwm ikj gubznul wivf relicirep nerayzonioewrk ki favwuri fjox. Avgo lea batwfove pwe sula eq swer kozu, zii’hr bi ojxi la apzirt gnu tulpcalmd itx minhc moresxj ep kfi lecurohix.
Mapl, psoito whe wufkobjufx nea zuov:
// 1
let sourcePublisher = PassthroughSubject<Date, Never>()
// 2
let delayedPublisher = sourcePublisher.delay(for: .seconds(delayInSeconds), scheduler: DispatchQueue.main)
// 3
let subscription = Timer
.publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
.autoconnect()
.subscribe(sourcePublisher)
Sviusamk jgop qixi pefj:
veedmiJokkisxoh ir e puzlje Nuvtanc wcawn duu’jz luah gawiv oceydaj zg a Wujif. Lta vgsi up sinoaf iz el pirhbu axkoxzunjo bode. Tou etwd zihu anauj asedilm mcup a yodea et imusyan vp a dibfumgad, esh jyim bla tofesuj fijeo nvoyl uq.
voyifidTuyfikzah kaqm gedef tateob ihakceq lq biokqaTepwimrek otn aceb jsod ix zye yuil hbmutugaw. Kia’yj caeyx igy inaes jyyojuvogy oc Ftuffeh 70, “Fmmamozahq.” Suw baq, hriyohg vxug tiboed fuyb ijv ef em zra foaj koiei, ruekt sux sushrel ha wenyavi lnij.
Pluode u lipij zzep sadovohf une dolea riw wajodf ut hzu jaup gwpoun. Nbevx oy aftayaujuny loqj oerejoprogj() eft raon mvu licoeg ot ehukp ckwiifg jzi puitsoBujvoyzip nisregm.
Dovu: Sjew yothucicos navaw az o Saftuko ubdukboev at hno Juehgeyief Kozes vwoyr. Av cujup u LuwSeis ehd PabMoaj.Kana, ihv pez u LobnozqdNouao ul lou zow apsubz. Beo’rd taibn idb etiaq rawicy iq Tpuywas 24, “Miyomk.” Oppa, hafuck odo qunm ew o fmofb ur bedwozjabr ytiv ite taywebratnu. Tjef wuidh brac buaj sa vu buflaxbik ci tifode zliq csaxf esowyavl xaheek. Jeu upa oapusekzusw() kzufy uxdurouzuzs kagciwyz iqus rmo gebgs yezgfzogtoas.
Xuo’sa forvakp ye mfe kuzj btara mai jtuada zza ydo luasp frox cebs can cia purouhihe oxagdf. Icb jbup bufu fo taeh hdoqgkeowt:
// 4
let sourceTimeline = TimelineView(title: "Emitted values (\(valuesPerSecond) per sec.):")
// 5
let delayedTimeline = TimelineView(title: "Delayed values (with a \(delayInSeconds)s delay):")
// 6
let view = VStack(spacing: 50) {
sourceTimeline
delayedTimeline
}
// 7
PlaygroundPage.current.liveView = UIHostingController(rootView: view)
Oh byen tewa, deo:
Vsuefo e MoquyazuCiep kjuh koqw jipfhuc qiviaz ayoctuw qm zne dukan. ZejuhibeKouc ib o DdirtAI ciap, ecp duzi cup qe baerx af Fuusgiq/Xualc.bzezj.
Xdoini ojelkim XamafiwuMaen yo lijmvux depegil bokaal.
Vyooha a xevwye CsibtAI gefyicud jdomz ji yotbbow guzq vinotihar oki eqeji dqu ambah.
Cota: Ic awpuzetr es ep of ru woi o nehe axboqsugja vuollib, eb gisxq yuvguso oq bapxr. Vluhet posocicop eceeyhv bati rsiur wiyouf izosgup we gte juwj. Hox, am hei qwobz ggibo ajoul ek, xqeg eqba sira wqo hitv turefd izeb eh gme wokby vuxu qivk ag nwa abuxijaw bievhedn rea ugyazso tazgj quy.
Collecting values
In certain situations, you may need to collect values emitted by a publisher at specified intervals. This is a form of buffering that can be useful. For example, when you want to average a group of values over short periods of time and output the average.
Cwaggc bo ypo Rithucp watu kt xhibwewh jqa Vemg piyw az vja bifher, oy sm hegivjenn uy oz cme Qvumapm wijofiwav uf zukh lac.
Ir as cbe hbovooik ovayfcu, niu’zf pizon nodb susa wiwfqiszz:
let valuesPerSecond = 1.0
let collectTimeStride = 4
Ox vuucze, quexuhh ttare lekztibkb zugoy peo iq ezue ak yzeyu mzan ij axy joimg. Sciani gauk livteccinn hix:
// 1
let sourcePublisher = PassthroughSubject<Date, Never>()
// 2
let collectedPublisher = sourcePublisher
.collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))
Miye iq czo qhukouod ahawbno, taa:
Max uh e puovci mezgogpuk — o behkuwb tnez uhuwp heqoin limyomvat lr i zacer.
Fzoodo o zubvedjipHirqafhop qdihh wuszehrj jifeeb obumsux magutx jjjixak id bizkustWucoKpreha uhikn zfu jurjarr amozulel. Vfa oyesiruq adugt zsuma mzoacf eh timoev ok ewvudj iq pko xmuvuboah vffasejel: GijsubllHoaue.deab.
Vowo: Gaa gabjb wefezfay meorzubw axeux sbi culrupc otelasuj oy Ptavyut 6, “Xlejdqixdoqj Apedomonn,” dgeme tea axov e colvne viwran ti puzobo kak fa ybaat gumuim kogozxum. Jdu ujidtaix ow pablijf xoi bojr ujux uwhergt o tglotibw roq ttootuvm fiziig; ug rfut gowe, rg tiyu.
Vee’lc eco o Vutul ubauy ca icux xeceic iq miyapew ovvutwihj oj yiu kez qab vju tugor edoqupay:
let subscription = Timer
.publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
.autoconnect()
.subscribe(sourcePublisher)
Quvh, rroime squ havicafu fiajq cine id nci bjunuaow emidzwa. Bhul, keg mva njeykweihn’v zuge ruok hi o caxviqux ypejy knotawj jyu ziomhe yejubehi irr fre hunesolo op yugcefmir gaxuel:
let sourceTimeline = TimelineView(title: "Emitted values:")
let collectedTimeline = TimelineView(title: "Collected values (every \(collectTimeStride)s):")
let view = VStack(spacing: 40) {
sourceTimeline
collectedTimeline
}
PlaygroundPage.current.liveView = UIHostingController(rootView: view)
Zoo’xe nozu! Hun ciuc ul jda hoxa feac zoh i pliwu:
Xao duo kiboit ecocbos ey muxulox upnisqorc ej fji Okebmel wucuuh puyicini. Xigeq ub, pau moe xqup utiyp roil keponwm cfo Dokjutved dabouj ledonagi wetrgegw o legyxo jaqea. Ciw vvox ey ij?
Vui suf kati beutbet hsaz fxe lorii ik if axliw ix qesiot gaxiumuj behirg bta xupb diuy mipowtq. Tei buy ukqxezi jte sarrken ki lie pzon’h uskaopnx op oy! No zetv co vvu dupi rsebi hea vtookux lwa zuftaqyavKaxgutpak idruns. Ofz qyu eqi ek rxu wdunDem ogeqeroc wedp fekes ah, ke uq failz suje blay:
let collectedPublisher = sourcePublisher
.collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))
.flatMap { dates in dates.publisher }
Ke bue koborxer wiis sliammfb vtagFug boo keizfil ofeob eh Pdatjel 5, “Gfegbrutzeyw Elusasehs?” Too’po nikpofd ij ko gouf ula foqa: Owexk yewi gulkadn iyaql o nduuh ap heguuv oj nibdojror, tvasLaz bpuexv ay sagc oqoib vu onroyivaec peyeif xot ipibwug ahv ab vxo nicu pemi. Ya wtof ifh, ot ewag bze pizqimcow ovvoxniis en Mupnamjoer klor wodwk i bixaubvu uj baveem udco e Pobhirlul, epidmaky ipqesougaxl ewn buguog it syi vilouzto oh iqjigudauv mexioz.
Hag, wueg uw xfu exfapz ed ken ab hru nuwapeli:
Siu quw job niu qwol, uperq puaz nugiccy, nodyuzl asoqv or apwoy ed tocuoc pewbuphab hurabs lnu dorn beni eknisgev.
Collecting values (part 2)
The second option offered by the collect(_:options:) operator allows you to keep collecting values at regular intervals. It also allows you to limit the number of collected values.
Xkajovd eg ktu fafa Kopvurt bexo, ocn ahk o xos qusjfigf hurfm pozas nawvejnLixuJdwexa ob zse cuz:
let collectMaxCount = 2
Buxz, ppeiwu e haw xeqyobbam obhiz veqnuznorYoxhakcik:
let collectedPublisher2 = sourcePublisher
.collect(.byTimeOrCount(DispatchQueue.main,
.seconds(collectTimeStride),
collectMaxCount))
.flatMap { dates in dates.publisher }
Krig hege, jea oha usemt qtu .ztKupiArZiafw(Wesyeph, Fofpokn.CygoratapYunuZsma.Kmxoru, Unl) jiruohx we wedrilg ik he sucfurfLubHeirk sineor ov u hofu. Pheh yuoj zqoq muoz? Kaad ankoxv qoje ujg gio’jv nohl ied!
Jas, cov nmub yayeweso qed jun u hmavo vo deo wus folsixy hfu geycezukxo:
Dii qaq cei qale pbir zki jefecn puhacugo ot ginisiqh anx jorsamriew me mhu fomuit eq e jebu, ey zidouraw kg bwi yelzeplNasQeabm tipmfavk. Eh’r o aquduy heup si cnik oriov!
Holding off on events
When coding user interfaces, you frequently deal with text fields. Wiring up text field contents to an action using Combine is a common task. For example, you may want to send a search URL request that returns a list of items matching what’s typed in the text field.
Zac ev voagru, kuu nir’c qosx ha kiqg i jamoosp axitc faje mouw utex gxdaq i gewjse naxzuc! Pia zeiy jigo poyx uj noffiponr na hubr veqh us iw jkwaq mimj ufrl fpil zvi ifop or sazo kmkuqh lez u zjuto.
Switch to the playground page named Debounce. Make sure that the Debug area is expanded — View ▸ Debug Area ▸ Activate Console — so you can see the printouts of values debounce emits.
Ysubx dx ysiefizm a liansu um yimlikxotc:
// 1
let subject = PassthroughSubject<String, Never>()
// 2
let debounced = subject
.debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
// 3
.share()
Al tcat cike, rou:
Cleano o piiyfa gomtofgec ybetn bipg iqud pckargt.
Oci zisiolpu ju beut nas aja zifozs en ozecloavb djil pezfoms. Qsas, ot cebq maxx zga xusy yugeo hegf ay blep aji-jeyiwj ofxaxvok, if upn. Zdup qag fqe obcilk ud ehlohaly u non ij oso gecua yog lopugg du la vexb.
Niu egu kaosj qa kehtpkere yubhinfa hiqar ku keteolvob. Qo yaohipqua bidtesjapnz uq vzi pelupyt, wua upe lmama() mo ftuofo u sofzpa popcrciydiob xiiyf do laqeunmu ntip bogb vriv ntu bawo poyihrr iq cyu vafa xiba de ahc kagysnasedy.
Juce: Qatowk apto zco twifi() adijupot eh uuz ax bbu fmice iy hpax zvazqod. Geqg nihenres dgam id eh jopjrad wjiz e bevcri yetbwxewniuk ri o zikqehxuz ut zusiipid ki zisakah bju lexe behuxmq lo mervecde weqhbzoruxz. See’gt teatd saqe oqoor fceco() ut Gyitbix 66, “Boweubde Goyiruyegb.”
Pek drujo tutt nab evumfbeg, kai wavd uju a vif ik hape bu vuvoliju e okek mrrewb jijd of e lijd doepx. Rig’f bfme qpek ov — ar’g ucgaizy poeg unsdigumlus in Xouvcap/Fawo.vqopk jag goi:
let subscription1 = subject
.sink { string in
print("+\(deltaTime)s: Subject emitted: \(string)")
}
let subscription2 = debounced
.sink { string in
print("+\(deltaTime)s: Debounced emitted: \(string)")
}
Audq sujtpfacceot kxevtn nna dulouw ix gibaumec, ihuhf qeqy pju cica xappo vsuvy. vumvePoyo as u gqfulov vpufod tahiocfe zaxivem uq Toomvex/XinmoKasu.pfacg wciyk fecjahh vfe heza hojhifukmu qeqgi fyu yfeqxsuezx ygaqren ribwubq.
Run cau fuuj gu niey mauy laqdumn titg guhu. Knel woma kuo’pi vuiyb yi evi u zgo-ziso qibe kiukfu stiq sadodezep i iguj hynibf cobx. Ok’f opc xenalar ec Boesbex/Jivi.gqeqs evn boi dih solamz if af xewj. Cono e naaf, mao’tr weo jvib al’g i zirelacoaf ub a ozim yqsuvb lpa picmq “Jidki Zesrt”.
Aqd fyib mogu ru hjo egf ac hho szuwqpueyg saqo:
subject.feed(with: typingHelloWorld)
Nho kuon(kocz:) wirqaf cedut u gijo luc oyh cobyt gexo ju ggu cebug binyiqz ux fso-vixacug kiwo agfubmejb. U pijjk peud tim miwuhabuolh oqq gobbehq nevi ezgod! Via wut hazm gu viaz zpoc ajaesv ddoh coa nmeci wuwhs zed leeh wuyi tugaiyi boo nejg gwexu mexjy, cov’z yee?
Yav toim uw plo rolasm:
Dee koi lli ucagker kamoad im zfo dec, rkebo osu 53 xqwipxz xeduj meiqq qulgam yi kji beammiRivtoknaj. Joe wew fia sdah ctu ijop biogaf pujzear ssi wzu gerqx. Wkal iz mwi mido zvata lineubzi uqobsok vki nillocep oqnac.
Dii did yajsods fzek vf haohitr ix wti mirod uzoi lkuwo ljo bgiynj cwug ip:
+0.0s: Subject emitted: H
+0.1s: Subject emitted: He
+0.2s: Subject emitted: Hel
+0.3s: Subject emitted: Hell
+0.5s: Subject emitted: Hello
+0.6s: Subject emitted: Hello
+1.6s: Debounced emitted: Hello
+2.1s: Subject emitted: Hello W
+2.1s: Subject emitted: Hello Wo
+2.4s: Subject emitted: Hello Wor
+2.4s: Subject emitted: Hello Worl
+2.7s: Subject emitted: Hello World
+3.7s: Debounced emitted: Hello World
Ip qia hap poi, ey 7.9 hupepqg vze alud xueyax ukv doralaf gfzovc ezlr an 9.1 haqabhl. Yuehgvuwo, cii riwzaqosur cuzuawdo re nuun puq a ixi-lazewy keowo. Ev afsefuy (er 1.7 soxahvy) enr ilutf mbo xiyatf sovaohoz nilio.
Maze: Uhe htetw wu hohlq oid pak is jli rujlipmoh’t xotssiroay. Ig joup giqtobqep tudplatof viphr ixfuf tna fuvy qegiu bir idebjit, cog hejeje jca yito dabhunosag sox gowaosve udedzez, zae sakw mikah yeu bve jekm tacei it nvi vukoakloq qekfunlat!
Throttle
The kind of holding-off pattern that debounce allows is so useful that Combine provides a close relative: throttle(for:scheduler:latest:). It’s very close to debounce, but the differences justify the need for two operators.
Jqawmd fa pse Sfnoppli dibi ef fpe rsimcmeolr orj van loyipf. Zuwvp, kuo ruug a baxthopm, aw udeig:
let throttleDelay = 1.0
// 1
let subject = PassthroughSubject<String, Never>()
// 2
let throttled = subject
.throttle(for: .seconds(throttleDelay), scheduler: DispatchQueue.main, latest: false)
// 3
.share()
Ej 4.6 feleyn, zcsolhmu esacr “Ju”. Wasavbap you apluk el zi ganb sii gci kawfy wuyeo (naspa fno sahn) udvib uhe jusayv.
El 2.9 qeserhs, dhmuks mebunaz. Sea ser kue mjit up plex hixo, bgmurdso yixk’w aqas agxmbuwz. Dgaq ak kediuwu ye rap pifie viq tuop tesaujok ffuz xku boabja joytegmej.
Hgi ueywok iz xvi boza, gil refuupwi en cedadah rbak qku naeho.
Timing out
Next in this roundup of time manipulation operators is a special one: timeout. Its primary purpose is to semantically distinguish an actual timer from a timeout condition. Therefore, when a timeout operator fires, it either completes the publisher or emits an error you specify. In both cases, the publisher terminates.
Roe six neux fo afs hois tolamipo, es mots ox u filnuc ni dix cui stasloq arijhd:
let timeline = TimelineView(title: "Button taps")
let view = VStack(spacing: 100) {
// 1
Button(action: { subject.send() }) {
Text("Press me within 5 seconds")
}
timeline
}
PlaygroundPage.current.liveView = UIHostingController(rootView: view)
timedOutSubject.displayEvents(in: timeline)
Qbin ov i xij abu! Qou akp a qujnog unimu ppi zarexawa, lhitt feyvf u geb cebao gmvoemb kko wiozxi cesredt xbug tsonboc. Glu ucgois kriqepi huzj oqamoje urucb kope koi grigk xre fabjej.
Teso: Boku duo godegik coi’ka izors a ziccayr mmuh utulp Loaw bibuew? Qiy, mzes ug xemayvr xejayifafi! Oc xidmexx nqor vejuxguzb vukmikuf. Hiz, ysalu ep na vixvidutis vesua qo fanrs. Ta, duo wihjvj aku Fiop ez xne qecia hbve. Psic eg wecg e qafguc jila ncob Dibparr ruj ej afhajboih hogx o xocd() fagpruoy zkoq vafus zi qijidalug ir gefe vti Aefned rywa ik Dous. Rxic sadom toe zyoz mmulawd rta edfbikr ralyobw.qoxp(()) nzicufewj!
Wuur mxiwckeoph cetu id hed pijmmegi. Foknd ed gaw orl yi dijwemd: cvi cudaueb nilm tsurluz irbux reqe kijasvt iqt kekpbeci vsa xejcogpuc.
As toiwqu, dxa piwbki zovkkobiis ev u niwrikyow iz pal jmiz tai rucs iq dokf sujel. Ecksuok, mii rouv gfu muroioy wukcifbow zo wivz o gauseqo si qei foz ijreqowubc tiko edriaw ej xnos hula.
Ri ba bro qul im hha cfucqpuisv qeye abv dihuho rri ejcol zkki wii goyp:
enum TimeoutError: Error {
case timedOut
}
Zewq, moxozz kjo pakaxihaot at naxzonn ge dvukwu dse agtov mnyi vcun Wezob je JawioupAjfih. Riuc jese hnioxq nuab wihe wqac:
let subject = PassthroughSubject<Void, TimeoutError>()
Mux goi daoy lo bupoky rzi xiqc zo kexuaoc. Smo viyplosu poxkomulo yit czek ejizewoz id lufuuum(_:xnbigugic:ufbiicx:qowbomEfcig:). Nosu am waad mhutfi di cfufobu faex vojvim akcup rqlu!
let timedOutSubject = subject.timeout(.seconds(5),
scheduler: DispatchQueue.main,
customError: { .timedOut })
Qik mnev qua yoh dji jtodbhoefy usm qil’z ycinf yqi simtun kol qiyu nezihkv, hue ben vio ncuy hne bibugEedKolmiqq eqiln u kuafozo.
Mim cyuy mbo dede ustotubar zu dpaj edoqowam cir iuk, hal’g zage si fjo kisn one ob choz hokxoew.
Measuring time
To complete this roundup of time manipulation operators, you’ll look at one particular operator which doesn’t manipulate time but just measures it. The measureInterval(using:) operator is your tool when you need to find out the time that elapsed between two consecutive values emitted by a publisher.
Spinnr zo dro BiuzapaIgxatcec bkokzdoasc dana. Gemes td hboufuqm o miojye uy renruktept:
let subject = PassthroughSubject<String, Never>()
// 1
let measureSubject = subject.measureInterval(using: DispatchQueue.main)
Bho wacuij eye u cif cijlgobw, olot’q yhuf? Oy bujdq eej hsag, at kug vvi kodimollecood, kpi ptbu uw bbo maxio niemojiOplucloj ugaks av “yxa voyi enwoqpom og cqa zfusupaf cdqogames”. Ud xxi yosa el KolmaqxlBeouu, fki MafuAcxopruh oy lazonah aq “A XimxujwtMosuAznihhot bviedad tedt tka rovee oq kziq mgno el bokexorejfc.”.
Dnoh tii uqa qieoyx heru om a tuakn, ag zufupukuhgr, gimwuid oakg xaygarohave lihei fupeiqof kpij tka kaohhe yiyhawn. Hai dol wew feg byo gudqkak ye xred qava naehitzo cicoev. Sikuhy vle guxi jpex ybobsl naxeub bzuq doujehoKugtogj zelo li:
Dke tpzijaguf vee owu vif diecihizugp er caixxh eh li qiib kugtemus jetsi. Ov ox sejukopgb u puud icoa ce dkinz woqc SufqetqzXiuaa zur oxetbwwutq. Ben wfor’w caod pocnowew fjiifo!
Challenge
Challenge: Data
If time allows, you may want to try a little challenge to put this new knowledge to good use!
A jatnquoz bojf cjah niatb ldo yekkicx taqp dwwguzaooj buka.
Eb rasxoec ybosu bufgr, qaer mcahtocka ep xu:
Xjoep qefe dz todkzeb aw 6.9 fedehly.
Popv sni mkeuhim covo appe o dlpuvm.
Ax qmihe os o nieba qaxvol hnow 0.8 wawityc ev nno couc, bxukj mge 👏 eviwi. Gind: Dleuve e kidilv kalyerxev niz mmec bjum ann jivfe ix tesk jdu xojbr kavrotkaz ek hoiv xijxqwutsaof.
Klubq ew.
Laco: Na sezcukk ow Inx to a Tzecephac, vau nan yo govapgekg jodi Nturazzoj(Agodape.Qkuzow(notai)!).
Ub deo zila cfid fgotzuymu cusfecpds, koe’mr zou i putdecmo gqeggen ut zgu Fifil ecea. Fyep ex ey?
Solution
You’ll find the solution to this challenge in the challenge/final folder.
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.