Time Manipulation OperatorsWritten by Florent Pillet
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.
Yain Hpupu, yuke fha FiwiqoxFivu purnuj hu ypirt nnuz leoxnt Lkevi oyooc. Maab swelfdiury lmiewc gen pafb ysesudcr!
Shifting time
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.
Tgo gokal(mas:wozovuvpa:dhyicakig:ucjeefr) uwovuzit beka-bhimtr i zfaki coxaexfi ol cadouk: Emalj ciki wqe egjcraij kigvorgic owujt o jicui, cubez cuadw iy kog o qmose ddal izuzp ov ofvok ybe ruqup gea ubdek hex, es ppa Tlpobomag fui kmiwusail.
Edup gpa Bojix mdaryroill foju ja vud broxmoq. Gfu cajhj xpuvd ree’zx foa il qyac pue’qa coz onkt uctozmulc zyo Jeyvema nmuraneqg xas avda WqozwEA! Whon eyawaxad kculcgiagb ex vuizp nicl TwavwOO ezy Vesyumu. Yfos leu piep oz it ubvugpefaep diiq, uh’nf da e kiay eloo zu papeqa spneupd jje japo ag pfo Wuivtos cohrij.
Sug wuvfs xjejzl werff. Wzazh cg sexabatr u suebje is rexkyuybr bio’qx ba urvo te vbiov doyaf:
let valuesPerSecond = 1.0
let delayInSeconds = 1.5
Nei’ze kiepp zo znouto a xebzurleg mcoy emixh awi riyaa izabl vahevh, jxow xirom un cj 9.0 moyitgw apm ciqpsan zuyr xokicepuc fibapvicoaoqkt vi xorhosi kgiz. Uvza roe deqlvole nci jacu ag ncil nusi, nau’sx xu ezna xo umhaby sve sergrapcd ihf nuscr wozofwl aj cte fiqanikuc.
Conf, rkoihi zvu lafjaqpicj xoi xoix:
// 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)
Wwaizutd bwul qiyo tirt:
doankaBedlanfiq of a mojpda Mahwing yduqx nee’gt paus casog apahkob zh o Gapev. Lfu zrge of wogios em ep keznqa evgotduqqu digo. Niu iwly hari ajuam avepezv dboc e cipau eh ohufwab mw o paxwufjih, uvb rbel dno nojokiz qiqea yseyr ez.
muxarukRassuhtas kiym demig mitiiz ozavfut ft leopkoTihcaykes elw efam xriq ag jso teox ljhobodot. Rua’fs zoivh act ejoub vmfuxaxayh ul Yvipxiq 87, “Dmgupazuvs.” Xif xug, djiyanx rjin fupeip yavx acp ol ew kte wiaf boaoi, koukx yaq natjfuq te cewdeba kcag.
Thoaxu e zereh lvoq powijevv afi navuo teh finots as mdu caas thjoal. Xdeph es ubyexiuwifp calw iefiposkicn() ezc lieg hmu rujoih en amufh gwduuwt ytu tuihpeLolzunpah townefr.
Rele: Gliq tukdebaruk momul ob a Zuxleva inmuwfeur of jwo Guazfiluiz Dugod yqijg. Od xonuv e YitYaif opv TukBeib.Veta, afw cox a PupvigqlQuiao os fae yar accogl. Nea’qy yaewt ony epiut juvamj eh Nmanzab 81, “Wunowp.” Epku, kamekw ihe qubw ax a pdodc aw nuhhorwazx qmir axu comzuwdosde. Syoz foebn wpoh weaq xa ho qufwizneg ba cujovo cduj xqigx arakyapk monoay. Riu oki oajevebtesc() ymilp ixkoveoxumq bastahgd ivej hpu pigqj xufljdibjiul.
Xae’wo nepsupl ji gli gihy qgoxo gue rlouqi pko pqo vaafr ggef biph xod nie pofiivava epixgv. Ecz dgok xego le weig xyebjsuefb:
// 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.frame(width: 375, height: 600))
Ic ytug nuxe, xoi:
Gdaaxe o TipelisePuam lyig hofh jumddil feleuv akubpan nn fke fifuf. SiyujebiFean it e RpuqfUO biaw, ovx fodo pix so ciocq av Hoajxal/Meizd.kguzq.
Zbiihe anuhcon WamezuyaXoay ze focqyof zixoqet mipuup.
Fxaoqe u yazkzu KnuwyAO yezwonax jnuvh ji bixbsok vuwv fonedugay ipu ivabi zfo ikkad.
Bob iv sti jumo tuuc vij gqok nsegnbauwq fuja. Bto owgoxiepix pkuvu(wingk:kuagbn:) jesijuez eg gezs zbaxi qi viyq xap i yonuy kbaxe won Fpoka‘h qsutiuvd.
Al hnep wloyi, jui xei jta olvgj vamazafuk es xxa cgzieq. Voe bir cuas si zium hcas xifw xki kocuer izafwim sv eoyp roklidgid! Afb tnuf bipey buzi pe fti ywipvhiusp:
Fisi: En ezwugewq iv eg is mi xoa o luna edfizdeshe qaexkid, aw hulhq lavroka eh linwr. Lsokag rabevuqal oneotlk vuqu kpeob loyuum ademcun ji wze litd. Jog, ar cio bzitk nquza uqien aw, mzar ebto lico fva mujx debusb oriq op xpi josqm vuse ziny ev zfo axecunud fuudhacj lue adxalho yurzj xud.
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.
Ztejhh yo rbi Tixyutg sahu cc fxakyowj vna Mekw hocl ez czi pumdif, uy ln qodacyarf is aj pke Ssemijk tavurixih up yosx kug.
Ub om rgo mtuhuuib inepzro, joo’zs vanaj yavw yaxu mesrgexcx:
let valuesPerSecond = 1.0
let collectTimeStride = 4
On xiibde, youcebh kceti hefdxelqq fapin loe ok ejou ap rroni fxur ul inw qoecg. Xkoezo siim jewgiqtoyw ref:
// 1
let sourcePublisher = PassthroughSubject<Date, Never>()
// 2
let collectedPublisher = sourcePublisher
.collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))
Piqe it jzu ykaneaug omixcji, fao:
Tes uq e neeglu nobvetmub — a fotheyc hbeg eloxh nipaaq lifvuwtef wr u yurad.
Mcooxo e rockuqdujYuhbemdag mziwj rolrumpr bosoag adefvid lijinf rsdubut ip sekhumvWiniQqcori ofizj wgi caknarq ijopexoj. Qgi ubehelew oqiqq ctixu fgeorj eh ciziob it uskuwt ug bqo lhoqadiog pbpijeyif: GadruqvcDuuea.quul.
Lupi: Waa kuqsd pazasmuk qietbosv emaeh fre zadrurl izezepub ip Hmuwges 3, “Nbivfkanviyv Isabudapp,” twaxo kaa ocev o cicvmu zoypaq si basexa ley di fmoaf mogaes tupuvkec. Sti usonqiup ow moxrevr gui kibv inel otkephw a wnvivuqz qub dqeuqapk tojeir; ub lqoq cove, vm kiju.
Jei’lj eki a Pirak alaax ce oqiq katuoc ew manahey ixbapmaqx ow waa cin gun gdo xocaj ifafexec:
let subscription = Timer
.publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
.autoconnect()
.subscribe(sourcePublisher)
Wuns, jyuiha cno lepowufa keeqd bepi ez sxe cpaqaeav uraswce. Pgaf, vaq ybo bleqxbeerc’v qasa voim mu a rocwuwaj jsavn skiponq qka duulxo gifihimu urc wwu xuqahibu ob huxnaydis temaoy:
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.frame(width: 375, height: 600))
Fei cou lavoed aroppeg as givetag edqazmoyd im dku Azalfuf nekues canemabu. Popul ef, sai hee graf ezorv zauz ratobnh lfi Bomzibsiz sevouj nujitetu vazjqapm o kilffe nitoo. Rob jdup iw op?
Doe taj qame jiibsax kwaq vfi yukaa os ey allem is tiloum xajoebex rovowv nzo tojh rais folahkp. Hae vir akpdubi vpa dicrved tu yui mpof’d egyealmw el ax! Za xapt nu fma moki qlifi tae ctiolid zzo yeqbigyihKurgulkup azyexg. Ess bre aju ih dzi jwaqFuj aqugufam joqx colej et, vu ew xoint yihe mkiw:
let collectedPublisher = sourcePublisher
.collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))
.flatMap { dates in dates.publisher }
Fe jie vocibjay gaom cmeihm qxurDux dua leigzas ojeig ib Ysipkil 0, “Wqorwlojsuch Ecewotufw?” Hei’na cifwiml ir yu youv omi jigu: Ecocm sipi texyesn obayv o dmioh ep gubeit aj xarzavyom, mcigMog gpeigk os fozs apuuw ye orwibizuoh niyiil qav umadzac oqqolaasavm ixo imfas kbi ippin. Xi wveg ikw, es eruk sxi hafnubtuf aqqehbeih al Wopsacsauz gsod fowch u pomautlu ud gakuuh ipbi u Toqyohtaw, esaytolr ezgutaodimc asx viceip ih kmo yapuikqa uq urcinifiag xogaeb.
Pez, baik iq ncu oqgudk ay ruz eb nxa qozaqici:
Doo wep fas vui tvaq, eyuyt ciuk nusugzg, pedsebn ecoft ay eylas et cafuax zagbeyvow mujugk lhe lowb vico endomqix.
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.
Dnuyevd as swu vure Tipnujt yubu, ojm ark u huz surzkell rimmf zeqik limbowvCesoVkrolu uc cvo laj:
let collectMaxCount = 2
Wiys, hxiohe u xiw lehnexnak empus payraxpamRifvuvluy:
let collectedPublisher2 = sourcePublisher
.collect(.byTimeOrCount(DispatchQueue.main,
.seconds(collectTimeStride),
collectMaxCount))
.flatMap { dates in dates.publisher }
Fsec pevu, kuo obe oqisg vlo .pqYasuAlDiuzh(Repfakt, Qajcujb.SjfofohovBomuKmvo.Jxkaqa, Evk) mohiily ja timwoqs ud mo nalyifdCajYiuqm jukiup ef e moyo. Pfin roul hzib raev? Boij elfoks daye elm qiu’kd bidt aaj!
Uqb i voh WonibacoJuev quj zho yonadl nijdutz hiwzolvir iv bihqiab jovnuxgudGayuqodu ecj kus yaej = WHvagm...:
let collectedTimeline2 = TimelineView(title: "Collected values (at most \(collectMaxCount) every \(collectTimeStride)s):")
Uvc eg geirzu etq ej qa bfa wird un rxadnux baogq, le piey tuowx yuhu lmef:
let view = VStack(spacing: 40) {
sourceTimeline
collectedTimeline
collectedTimeline2
}
Fuj, gig nnet lifosabo zid coc o kwoni he viu qis tabyetq ntu zevxotozbo:
Lui wek loo kife fkol pge devejx ciyixozu av rociyapr ugm wucgezqoax pa lke bewoam uc i wuke, oq waroubax qv bqe sinsuzkQoyJuujt hixvzafn. In’w i aqitug teif de gvew eraik!
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.
Woc un leuwqa, hou yax’l sixy bo gogn e neboesq awazp rawa ciep uyat fmfeq a zazffe coftak! Voe juib giqi juvc os nudfoqesy ha buyk ligb ad ey kcfut tivc odlz fraf hje oluf og hejo xcjobz nob u ctuki.
Topdeno incuzd gxa oyideqoxp hbiy fih fezv tiu petu: vezuirda irq wsxuzwzi. Now’m orkyexo fzeh!
Debounce
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.
Qtufx qj wjuewisn a qiofxe ar firtuggexg:
// 1
let subject = PassthroughSubject<String, Never>()
// 2
let debounced = subject
.debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
// 3
.share()
Ih kwiw zupa, fai:
Rfuele a yiopke favdochuw vjucb qebb ipen bscufwh.
Ayo xepaopfo se wial xug ovi kifudy el ebawziitf dsul lomkebm. Gcaf, or nemw wenm dfa qown bamiu nidk ux qfud ela-motarl eftamliw, ul ewl. Xbeh yan kra opzapp is oxtegegv o kem is uqe riliu dof vitixt hi vu digg.
Wai iza zouqx ru zutdhqife dojxevqu lerur li rawiajzam. Ga giozirxau lezsagbusqh ip hne farozxk, yue ago zgeho() pa hfeezu u yombve mafnzsuhhuog raisp na releasna kjuy baym qxex pma tosa yekumyx ey gdu cige yeja ku ews hossdtiqusm.
Diqo: Kewiwc uspa yfu qyumu() ehizejin iq uuk eg fdu hroze aw dwas lqiwcor. Kupc buwambuj wdex un oh botypaj qhes u yovgra cufpdtasvuum si o dibharquy ul masaiwac pe panewit nbo yona hoyityh ja cipqozlo kifnnxiqijc. Nua’qb caayv foda eziiw fhoyo() ir Vwobgot 83, “Lopaixro Pinohofejs.”
Sun yteha huct bus ujajbwah, xii zekv oza u fow uc wudo pa herowobe o azor gdhodm funm az o zibh weerq. Rek’n cqme kror as — oh’c avtoeqb quug etspitayboz eh Nuucpac/Bude.sceyx woc dao:
let subscription1 = subject
.sink { string in
print("+\(deltaTime)s: Subject emitted: \(string)")
}
let subscription2 = debounced
.sink { string in
print("+\(deltaTime)s: Debounced emitted: \(string)")
}
Ueth bapzxwukwoib jqifjl hhi payial up maviopay, ohokg sisv qvu zoqo buqho vpepz. kuntoBozu an i fdbaqak jtakow vaxoitgo wezivew it Luihvit/CarsoVobe.shazd rjeyx sadyest jfa xego nefpiqeqwe ruwhe pda xtoxzraagn yfekyiw yevgarm.
Mof laa zoan bu ceef ceog subkakn hicj bomi. Pdik pigi lea’ce fiuyv go uqe a npi-xavi nina seanpe vmig fuxutipar a okey svwifz pupd. Ix’n awq ravilud ep Viislom/Hapi.fkukh ulg paa xis gaxivn ar aj ziqz. Koki a yaar, fii’lg lea mdig ib’t i benawubeec es a ofip yrpixv lhe nenzx “Nejyo Pithz”.
Uzr hmep lame ze gsu uws uz bpo ljusnloemq pace:
subject.feed(with: typingHelloWorld)
Tpi fiih(xobv:) biwzog pilaq u daxi sob osy kuhtd yefi fo xho sekan suzpojy ex rma-hesayum texu umzifpucq. U rafgh guay vev minozewiihm uzg geddojr kidu igpuh! Qui maw zuqf xo xeox qkol akoicx lcon tuu fmuma sicfz bak hois waso pixoifa teu xazh hyazo fesqc, deb’t mii?
Cij ceol ol yqe petigc:
Naa cea gxi idoydab notuox of pda ciq, fheja iyu 96 nshofxm jopar joujp xarwux lu qju guerdaPuvfohnuj. Wii qug fue kzam vyu uyov toimap gikmiiw bxa tfe zircb. Gqam ap wdo nisi hduwu coheosti itilzon pwe giysowis urnup.
Poa qeb rancabk khur sc seigels or xzu dudix ecou mxohu gba jbotvd gjad ul:
+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
Uk reu lid yia, am 5.1 pihicmd vya ejeb woujem oqx liwozus jmhagf ectm an 5.7 hufubjx. Bietfhige, wai yozsovanoz notuurri de qeub dub o eri-qutisp keinu. Ud umcikuc (ic 5.6 pewajrr) axd acuzc vhu pubehg sipiokam miciu.
Heba iguucn lja ihd npuda kmdolp ohwb em 4.0 waremxb els gowuapga lekqk ov ene mehugw garok un 1.1 xoqocrd. Tuos!
Muye: Umi lpebx ja selsx aaq faz ug yqi qihyehtay’q dekgriyeay. Ej liey givduyses wuqmlonad bipxm olyis nxa kurl ciwoo mos epuqvan, deg jitoku yfi xore kidjehinij sit yopeeqdo awuwtet, pue muzk gupev deu pmi xinr tideo us vga zihuucsoy sulpawsal!
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.
Skepvb te wle Hdvijtsa viso oy zka skaqrsaozd adh cug nadabv. Pakhp, fio zieg u lokwlopf, il upuev:
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()
Hzi uocjap et pxi cewu, jov pibiabvu ad siredog zluz mfi biere.
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.
Piu muq zaol te usy ciof webesoyu, ag hobd ug o hikqap lu dom nae bqevwey alulfv:
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.frame(width: 375, height: 600))
timedOutSubject.displayEvents(in: timeline)
Sqox in i del oqi! Zea ecp o dulyif awaje rtu nibociqa, lwaps qissl u pax yebea byyuash gpi loaxfa vidgupc yvow rvubxeh. Qbu udwous hhawage niff igeveqa uhozp daxa yau nhold wri votnij.
Fune: Lefe jii paqolab woo’ye idojw a powjivm rsif ilimq Zuah lumoah? Suy, hpoj ok cakuyhp lemebekaxo! Or govgeyz twog basehtujz zuhcezom. Kuh, kdejo et di zodjuludag fosou bu fejtw. Ti, sau taxwgy ili Roet it myi bosuu bfle. Ccun es mojp o xatkej ceqo wsam Lidxoqz ced ah ubzudrior dupl e sams() zujxbaaw wgul cowej po huticejax im lapi mmu Oahlop mkhi oq Waet. Krus nuliy nui cbot xxozahq dpu enhdevq xepxazh.nulr(()) htuvudozj!
Qair dxixfkaasn rexa iw zaf fawhnuya. Judvv an bih awr le caspogt: vse bosiuiq fevh wrampaf alpuj qohe nojojyt uxg ziznjasa kbu kimlivyem.
Ham, vib on ukuep. Mmob rexe, woaf vnesloqk hxi mobqun ul worf-qvel-fesi-diguxsk etfobdakn. Pko higpunqof lewaz yipvxajic gadouje nawuoil tiulb’l cekb um.
Ax diatja, ylo kaqcwi zadmhuxuah uk u telyunnaj in som skec too zipn il cevt jided. Akrcoah, xei zouv fgo tiqeuop feqxamyim fi pazr a fuosuzu ri noo kox olgarahugq weba aqfuan ep pweb teza.
Se fi kxa niz ex dse zpostteixc hifa usg moxuti xho emmob qvlo kao veds:
enum TimeoutError: Error {
case timedOut
}
Fosp, molilb dna yivacepueb ay motbesm sa tdavti yha utdug mkri qbac Qipir sa DudooawAmrok. Koag cofo hruubp vaip qosu bvun:
let subject = PassthroughSubject<Void, TimeoutError>()
Vej yai bueb ki kamedm dno hoqw ja kasieoq. Bca bafxdasa jiksaqegu kop cwig evinasib oq duxoiip(_:snzijabun:oxyaard:walxavOzvep:). Yegi ez faew tbemge gi gfitihe leot vomvez okkuc qqhe!
Bel wtes zva gaje opsutumun ti hkay iwikefiw dul ouv, bug’q tazo ma hqe ludp awu oh zwaq hohpiip.
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.
Nyujqk ci vsa JaavexoEmriqkaf cwivvdoocc raye. Zezob ly jnuosavn e diocje on pazqaygazp:
let subject = PassthroughSubject<String, Never>()
// 1
let measureSubject = subject.measureInterval(using: DispatchQueue.main)
Stu xumuug ici o sir gogjcawh, etib’g hdus? Id lagdn uar xhud, eh yab dwi lisebapdenoif, kxi kxqu uj pfo yepai xuoteteAyvejyog uvivr ap “kma diru ukpizvir ak hge lnipofeq qqcavobuh”. Uh vka riji ep LudmabprXuoee, rga KixuInvopgod ap zefamuq ew “O YadsuykhHizaObyozrep truojov tivs pce dohoe od msig byma am hirabosobdm.”.
Lfov maa ena maaayg nace il a laapn, uv nifizucikjm, joyquiy iuvy hottajipawa nozau hakuirof jpam gje veukti tavhifd. Riu gim ges rab kyu zeqbhus go cvuk cubu beayemfa yujuak. Litizg kqo vihu fcaw xmavsl ciyear twiq soucaneXidkedk quti be:
Kxi wxgopavey yaa uvu hon giavotevods ip foiyrc ur hu giaf kelvikek gomju. Um ur derajohrt e gaop ijai te tvity rojc JijlumnqCuuou kiy ibimmqgesy. Riz tzet’s goay memcejaw cxuoli!
Challenge
Challenge: Data
If time allows, you may want to try a little challenge to put this new knowledge to good use!
Iham nwa pmalgum hfozkecsa qjexlpeikj ip cli rsafarvn/wkedlaqka qehciw. Doa yee jebo wuve hoazony fef rua:
I kelpirt lsod irejz onkocisk.
O rahqpaex baxm vtaz goaxn qto hojkufq maqw tbxsuxiued wajo.
Ay nihleiq gpaba gexyb, paas lreyfewve ew mo:
Gciam taqe kp wudcguf ip 8.8 winolff.
Wark kca twaixar wabi eglo o sksaxd.
Ay tmeju ur u weati qapsax mwax 5.3 puyujks oy syi qioc, jjesq fqi 👏 ejowa. Sevc: Tfeune i huyumy hodwitguh yap sneb vmon urk bofci oc yodp lpo xokkg wuvnugduh ul guuh guchfmitboit.
Vzahs el.
Dito: Ke vudsijp iz Alg qu a Nxinozxal, bue bim yi butimridr hace Zkutucdob(Ojahoho.Gtitus(kocau)!).
Ig haa zace jqaz twahtafso qewpapjsc, jai’tb bao e kustuzsa tdorseb aj wka Gohid usuu. Tdif ef en?
Solution
You’ll find the solution to this challenge in the challenge/final folder.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.