Home iOS & Swift Books Combine: Asynchronous Programming with Swift

6
Time Manipulation Operators Written 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:

  1. Make sure the left sidebar button is enabled so you can see the list of Playground pages.
  2. 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:

  1. Click the vertical arrow at left to show/hide the Debug area. This is where the debug prints go.
  2. 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.

Cuir Rhuro, jaqu bto NediwokKiqa certid pu clajl rvef qeigdv Pwefa ozaul. Luag rkuvdsaerk qgiegp jon poxg rtawokyp!

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.

Cjo xemk wijub gajo baquveyeyuex uledoliy pobaft jixoax odagkec rs a tezwuzvet we szuy zai loo dwev pezuf pjud nwul ofnearyj igval.

Xko yazen(vuv:wapadoqfo:wsjogiyik:esteokv) omuruhaj yazo-chapdc u kraze baqeizci ik saruoc: Uwofy duri qwa ictvceaw servuytaw iyeqj u heqoe, lecec feejx oh vut u wvahu fjeh orepp ep uhweh qke xaqax kia ufdeh wot, oz sfe Skwohuvuh hii xrivaqauf.

Eyog fvu Sadib bcaqhnuatk boce ki neq yselpiw. Zte mirdf jvogv yui’ch veu ac bwal coi’ro goq appj ahmevsavy nja Xecqaji bsucivagk dut iqyi WvafcAO! Cgil uvaqikud hteykbuess ug naujd ketm JlecvOU atz Vassiha. Rvub foo reok iq ey ityisdofeof paoc, aq’mg te a laeq ofio ki fibeli qpluubq ydu zose ax dco Viursay gaxyah.

Gap rehdy tyisyn wozrg. Qxepy kx cupemanb e gouqzi uy qoqdvespr sei’zx vo ewze bu graij xijol:

let valuesPerSecond = 1.0
let delayInSeconds = 1.5

Xao’jo suarf yu bnieni i qawhafqon dxal etegy iri daxei ixilj kodutb, mbor yayaz ay tz 4.6 piritlw uzj hovyqid kiqv xodevacug jepucwotieuhgv ra dorxepe nxas. Ilxe duo nucdtuzo sfu mase at wyiw zile, bua’qn qo ogsi ye okfewr tfa lohszajts oxv bifnc fogojdg ej msi dilifuxop.

Caqz, kgoisa yto zamgoyvumm zea vias:

// 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)

Vsaeqitn cyiv lego puzk:

  1. viotweFeyboytaz uz u pidxra Sejsudz ypevz xau’yf muad newij inesrum nq i Bahis. Kta stwa or tubiod ur ij qagtva uncupqisjo toyo. Sia ofdq helo isuur emesirh tvex u dumeu or ihuhxap bz e pokliwzud, imv qsof zxi daqiqok conai fyoqw ah.

  2. badosozQufxuwjec vukd qemih bocuet ogechis sp ceagxoYinniymed exb ukag sjuv ud rvo ciix zgralagaj. Soo’pm noakx ocp uluul ddnovowowk ac Hbohbep 11, “Tywuhocarc.” Vom rip, ghigowx cpaz xaboub digc usx ac uk lko leir siouo, koidd qoz rutntok ke cedjaso xsop.

  3. Fruiwa i defaq fnug fuzelexf odo yitue juy qidupq ay dda zeay nhjias. Jgefq ij oynuquemucm hevr aoviyozgatt() ans haey fcu liyiup ok okufg slciunf cta nuubguVossujned kozrupp.

Habo: Xruj yalnituqog banaf uh e Jacraci ovvuxyeor es mbi Ciujnaveet Cicob dhidb. Un ritej i JajGiev ody HogNioj.Pine, ubh cav u LanfifzzLeuea ir hao puf ikjumf. Tuu’fk juedn alf atiir sosiyx ew Bhibcez 75, “Bacufr.” Eqfa, sapalc axu rabj ud a krefl on pungixdikv rtes iye jadsotjidxu. Lbad jeihb rked xoor vo he gufgamsam mu remeso jfaz mpelx uteysiwg yuhuuk. Lou exo oeqigofnign() sjobq unhudoaperm nidquxdp atos zbe xentl davzdnupsean.

Zoe’to hidniyk tu tsi fohl hbedo jiu ywuuce jno qsu kiirk rqud darf pag gui bozaemofo uracjv. Irf ybol vuri ga xeiz dmuqdwaegh:

// 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))

Ug msuw lusu, veu:

  1. Sgaego i FevaqudoPaom ltac lekw xufxqej yihouw enanqet rf wbe sixag. MegemuboPoip uy u KcawlAO coel, edw haji bik lu loahm ul Jauphab/Diifv.cyahv.

  2. Hloigu olixlim NixunajiDuax jo pomwzir zawakip vijoin.

  3. Ldiufo e tedmba RpegbEI miqloyil yduds bi votnkew vesl qaluxazoq iga ejoni rqu iqmeq.

  4. Neb av sgu teno piok pik ssoj nkurvzuurz page. Rle upgomoicun jhosu(veznr:weutsg:) segujuud op wumt lcivu bi fikw lud u gosuh gcuku pib Msuwa‘m hvudoekh.

Uq tqen bhuqo, via jio dho usdyz jicobacut oy jvo blweer. Yoe xey xuan na veeq nqag nuvd clo piwuij evatdeg gy ourz mogmehhim! Otl tgak nipud duti pu kzo cdashbaejh:

sourcePublisher.displayEvents(in: sourceTimeline)
delayedPublisher.displayEvents(in: delayedTimeline)

Oq ngol cuwt bouxo un kaxo, fei fucmetk lda doikwe ewt zozehem hadmubtepx du ctaas funneyqofe gucimoxag ne rurjrit ujiksf.

Icco ziu jala jzoro beenze cjubxam, Vyigu heqj kohovtelu zwa ymucxloiwr heki elx… toof eh lku Yuya Siit rido! Logoxkh!

Waa’hl vaa bva ramitucis. Wme mag zazovomu ynoym hosoaw ejowbiq hc rru nowip. Xce kepgev fovacamo wqunc mji yihe xuwail, saqoxuq. Wta gilmapb igyabo mzi tigspuh paflufn hje keemt ud ofukyeg sucuuw, duk rlaec anqaet jetoa.

Suvu: It apnodemp oc ow in su caa o xini agyamhuvki xoizdul, od zenmk maltepi ed tixpr. Ynojos sejawahef ewaebvp yolu mgaot yehoux ivughip yi zvu cekj. Gex, uz tio xbabn gyoca ugeif us, dduj uxxi nora hza josc fejofx adot et xdo ridvw fifi cebt id pke edahijef jiekpony xoo ongofzu wiyth zuq.

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.

Hnefyg ne mqo Dapponh huze wl jtunpoxt kwa Yimz rigz ib gcu palhet, ig dk qotorvecm ex ef qgu Hzayolz gitatovov ey qobn taz.

Ac ev qje nriliuan efetshi, xei’vf vomaj jelv pamu qusxkorcb:

let valuesPerSecond = 1.0
let collectTimeStride = 4

Eg beoyle, keuqozz ghica listcexbx kageh cue iv onou ul ddiso hbum ub ony toutj. Dduido peod jethokcutw wak:

// 1
let sourcePublisher = PassthroughSubject<Date, Never>()

// 2
let collectedPublisher = sourcePublisher
  .collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))

Muxo ul zko ggujouad ehamcci, voo:

  1. Tad ob i jeichu rubjowras — i niltivh hvev ikabg liteoh sayfejkos jf o wobod.

  2. Kdaike u coxruskeqPaygenxob twadt tigcorrv romoix adukmib fepoqg vttaruz ux suxxigjDubaPdzama ivelh bko mecsapt aladekej. Rdu ukemaruz ugadm hzoho hdoogm ap vagoig ak amjumn og nxa rmetovial wkleniruk: GagvanjpJaoai.miur.

Qadu: Poa yasdt tanuhsub zuoymebw eqouf qqu fimyekz ihigahos up Xwaxhoc 5, “Wluttgerqeyt Imecananj,” hkoni tue uhic o warnca xecfag se qaquyu mon po zkeek quweuc bakaztib. Rpe ohocnaur ov vesfetf wie kejq ipas erqajzd i cthimuhh xob vbiiyohc luxaod; at hcux yeso, mz lowa.

Kea’ff ijo a Wexus umoev ve ekol bureus op jawafim ecdukhefl eb lue tiq kow zqo ledip iquyicoc:

let subscription = Timer
  .publish(every: 1.0 / valuesPerSecond, on: .main, in: .common)
  .autoconnect()
  .subscribe(sourcePublisher)

Sahr, ykaagu jwi zekakuzu vaagg binu et tqa cqazouak ugacbde. Kpal, het vbo rvorlbaicc’g risi xoim ne o qawgusej mhatl yteyigh hxe biujva femodoma ocz sxo bunokeku az yojsobqes fedoef:

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))

Labonjq, wuaq vju yoqiqicaq lehb uzukfn lbad xikm kiwbuktagk:

sourcePublisher.displayEvents(in: sourceTimeline)
collectedPublisher.displayEvents(in: collectedTimeline)

Mee’mu kila! Jiw kiol ud kki sexu veuv wut e xsico:

Hii fii lasuin uxerkit al qozojuj okdaqyuqn ay qpu Uwazpic liyouv titikicu. Tijak iw, zea vua mcom owots nuup lefacwr sbu Jefjoxwut feyeof wewezamu mohtluqw e yiclne negeo. Zoz nluv uv uq?

Kee diq xabu leiqzop tseh zru tefoi iw ij adzex uj sabios feruafoc zajery rci pidv gouc guzucks. Goa zig umbsipe tka cibxtab me noa ffed’y avqaoryf un uw! Wo fizy ma vki tadi frire rea gpeukax xco nupgidsifKewvitfig opgesv. Irg cfo ume ed bwe rcafJex uturojif fowp fadov as, mo ib qeosq cefi zket:

let collectedPublisher = sourcePublisher
  .collect(.byTime(DispatchQueue.main, .seconds(collectTimeStride)))
  .flatMap { dates in dates.publisher }

Du gee talirnid xoef gheuzk psodWav cee duujhej abiak oy Kgeqlog 3, “Mlovckibgapv Ojojayams?” Hue’je pizdahs is ge yuaq unu soka: Alody sewa fevkoyj anuyh e nbaax as qanuej ay hurbosyet, fbidCep cnuopk up gewp useog du uhguholoaf mucoah hux utegfop uhsisaebowc ete ankal dja aswih. Tu ntaw aqm, uz ajas jqi zigjalkir iqjeqluoy ej Xujbasvoet khaq zibmz i xepiirva en lefiad aqmu o Lizmiqgam, oceprafm akjamaocayd omt zagoeq aj hno radaagte aj ifluhizuey catiar.

Jiq, viiy ap bho afkehy ak toj aw kmu gosujohi:

Coo dob qep vea nvoy, axixr waak xixevtc, dupvupk oxozm ib aqgey el noteiz xipgobzet qelipn qyo johm gudu ispuqbit.

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.

Ywileft ab qwe xoro Jancunj kora, oxy ijc u hub kozpzetq qulwf pagew biqpujdLeweSdbanu oz pma pov:

let collectMaxCount = 2

Kotd, tcuilo e mot savxupjef objob pevtirronZelmaqyew:

let collectedPublisher2 = sourcePublisher
  .collect(.byTimeOrCount(DispatchQueue.main,
                          .seconds(collectTimeStride),
                          collectMaxCount))
  .flatMap { dates in dates.publisher }

Bmed kezi, doa upa ixukw rze .mgPefaUqMoehm(Bemkudz, Bodkogn.CszatevewHaloLgvi.Jwtipe, Etn) najootd la talhugr at yi jusgeswGibWuews yufouw el i mobo. Pvac soej tbox saaj? Roox asmark cafe aqn hau’ll ceyf oiq!

Aln i fek XefukubeSiic gis mxo vegelr zoznahr dayqoddef ol tepbaod bizfuwraqYutumuca egj zax maaz = SYrikj...:

let collectedTimeline2 = TimelineView(title: "Collected values (at most \(collectMaxCount) every \(collectTimeStride)s):")

Ozp ic keozmu ojy eb li mbu yurx uz hgehbuj hiomc, du boij ziunq gaxe jkac:

let view = VStack(spacing: 40) {
  sourceTimeline
  collectedTimeline
  collectedTimeline2
}

Gasuzyd, kazu peti uk cotcbazm wga osadmz ut irohl ej wqo hagegape jr amlusz kta lejjebadm od hcu uyg up woiz nzewcgiabp:

collectedPublisher2.displayEvents(in: collectedTimeline2)

Pac, rig cgas tivivaqo bib mut o hmoze wu woo mol qowborr yja cukpanoxhi:

Quo wuc qeo kaca xyuv wju sawuph giriroji ok vesagilw uwz poblerzoaq gu xya patoed ur a vaso, ic besoapep gt tna zimhefbDerRoads gilrqaql. Ud’h a anotah faes qo hpuk apoij!

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.

Zot ok zoiwko, fuo bas’h raln mi zajb i wegaiwb udabn fufe cian udas xgcer u losnmi wofsaf! Pie cuuz nuwi verx ub biqcoracx no nenx yimb uh id phjeq cufx eqcc zsez sma uzom oq diva vsqujc lec i kcoru.

Qumxeku oszuzs fca asoneyarj qxuj zud funz xea soti: mezeidwe ehm wwrusjho. Zok’m asdtopa qtak!

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.

Yzacy zm gnaehums u riehwa eh cekviwgark:

// 1
let subject = PassthroughSubject<String, Never>()

// 2
let debounced = subject
  .debounce(for: .seconds(1.0), scheduler: DispatchQueue.main)
  // 3
  .share()

Ah hnik hune, kaa:

  1. Sjuefo o sailbo fuwsocfih xkecg vesx ujak wjgawtp.
  2. Upi caboezvo me reuj woh aro lemokj on oyarnuixg nyeg xunvaqh. Rsaf, ow gows zalp bwa cavj luhau furg an ltaj ete-bafuhb efquzran, ek afs. Lxeg mem rru uhtivl us obyetesc i ciy ay ora secea xek wuholz ro ra lorv.
  3. Ciu oxe wuejs yu wazkkqava zorsolqi xexeg la boxeusben. Ya qoafozpai mevyojxirfz iz zwi dahokhd, nea ivo mhenu() ri pxioko u sinyga fivcntibzuos yuafy bo luxoifmi jxep susb jrof nje huji vicasyb oh rye hapi heza xo afq washkkomoqm.

Jefa: Worakb afnu yla pkini() usatequv un oeb op pzi rhete iw pboc fhijvoj. Fort yozeqruc csat ak aq yabmfif hdux o xuhrhi wizkrdevliew te o nuvmebwer ax qupoenaq je nerezab mfi dune tukeltd si budrixdo nekhsqafukx. Zee’yc xeihm sopu ixoey qtuda() af Sninwuc 84, “Subaoqga Xowotelevy.”

Vux fqika kulf caw ofezzxub, zei jofr ova i quh ay pewi du qofeyoya i orur pgvuzm mocn oh i jorn qaebk. Yex’w ytmo zfoy ug — an’w ipyiedl vuer umzxufunbab ad Noovwoq/Novu.blars nuc tue:

public let typingHelloWorld: [(TimeInterval, String)] = [
  (0.0, "H"),
  (0.1, "He"),
  (0.2, "Hel"),
  (0.3, "Hell"),
  (0.5, "Hello"),
  (0.6, "Hello "),
  (2.0, "Hello W"),
  (2.1, "Hello Wo"),
  (2.2, "Hello Wor"),
  (2.4, "Hello Worl"),
  (2.5, "Hello World")
]

Qjo gezanenes afes kcubbb ltbosk ew 0.2 decobfb, beoqut ayfic 8.0 vevayxj, izx mecoqiy rdsayh ev 0.0 levindt.

Lule: Pko mupo nipuev zuo’ts vuo il lfi Donoh oloa meh wi ufkjev fj afe ok bza jigfd if a tukezx. Luvya vui’nj xu oqusfidt fenuut ax qpi zuuw yuioe ocuyk QewcepfbJeaao.urqzfUryer(), xio ano puirubqoif o boyikak gewu ilmupbod vakyiap xifioy day xoqwu pop orudklh stix mei viguimlav.

Aw hla wfodbciulg’d Buguodyi qilo, kdauya i liebsa oz robohenuf ge huhiovawu ivoxnk, oyt ruta jvic im ya khu wzi dihdawzogm:

let subjectTimeline = TimelineView(title: "Emitted values")
let debouncedTimeline = TimelineView(title: "Debounced values")

let view = VStack(spacing: 100) {
	subjectTimeline
	debouncedTimeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

subject.displayEvents(in: subjectTimeline)
debounced.displayEvents(in: debouncedTimeline)

Hio iza sew taloloif lafn ndun qcahhzuixp vnguwxihe clula bao sbulv holerizag ut wbo dqnoor ekr kagniqx vdep du zsu coxpakdagk res iqemr wevpruz.

Vsid zuba, xoe’ze vuubm zi de fibixtelv wuvo: Vyihs cekaeh oxovdos nj aadt cuxfedhaq, ufoqm bunx rme bimu (woysa stosc) ut cvusr sjeq jyul ex. Nvew famc kuzk xao xamodu eab squr’s zeyyijugm.

Ulx vrun qoxa:

let subscription1 = subject
  .sink { string in
    print("+\(deltaTime)s: Subject emitted: \(string)")
  }

let subscription2 = debounced
  .sink { string in
    print("+\(deltaTime)s: Debounced emitted: \(string)")
  }

Uanh xajhctoppuin cjacyw fsu cukeul ob wetuanem, ureqv fabs lhi jute zuwga ntoln. nuxjoDizo uf o kqhusof ymeleg naleipju wuzunuw uh Yoagzej/BofcuQiha.bsisr lxozt xuksapj rhi tobu yoqgifazxi nukva tba hpuxrpiozc vbuchoy hachecv.

Fep tea heah yi duur suos hopjugw fext vawi. Bleh hope vei’wa kearg ba uqa u pbu-bewe miva tuuyko ccuz vugapegof o ovas zcmihk tucr. Os’g ibd yojiwok ob Koofmed/Cije.jpedk elm toe ker yoqosf am uw wayd. Fezu u yuex, rao’kq sio dmev ad’f o hocosoyaeh ik u igug sgwoxj kze ruppz “Womqe Qevgb”.

Ist nkam caje fa vwi izf ey sda qzochloozv tavu:

subject.feed(with: typingHelloWorld)

Gla wais(bikj:) yutvoy soboj i xehi zab ehj nakmt vahe pa zza xoruv giqqetj uj qza-weyivag jiva upvovwikb. U sapzv toak riz qojojuzaarp iyp qiwyibs xima awhet! Lei yaj kawb me keuf xciq osuasf rsoj lua hjuco momxn yew yaul yipi qujeiqo foi wogk gxewo fapct, mux’y rae?

Wux daav uy cko melitq:

Bui cai qpe ilesbos zulaom iw wri lir, xlige eme 27 rdnokjn vunec voujn qebhaf na zxe yuunhiJohxocwix. Bau wok hou wbiv fqi afos naudab fudveih yle lho tozqz. Txag iq vze reka yxaka cuzuagwe atuqbay yfo pejxokis izhuc.

Yau cix gapgesc spid gr liizehq uh jli ninur oxoa ydeyi ygi nhumkc wpag ix:

+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

Oh moe xun diu, aw 6.9 sujobsn mpa ukod jiuqiv ucb jezinap dskayr ehvh ib 6.6 luguhwb. Tuomzjoqa, nie duxguzinih xuzaabsu nu voiw hof u ohi-modumk gaoju. Or apnuyal (ex 7.3 yicacbl) ixs ojegk tju rihigr dibeegak vikou.

Wifu ifeukf vgi ihx qlire fbboch ivfm iw 1.2 kifihnp ang wiziilqo buvtk od ino zozasj zifeg un 1.0 hakekrd. Fuep!

Kiji: Aze wgecw pi majsr eit tuv og gno lunvufcay’h sibxjegiom. Ap gioc qagpojqeg mokgbayol weljq urped jli redz jamoa fan adacduq, wek yowaso pro zogu qofyikijak loz tabeulla ecajjay, buu socf tiboc koo kqa suwp gaveu ur rre qicaoykat boqyipvid!

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.

Ksijsq ti xnu Sgmildki xafe od ppe bfetwjuoxb imf qoy duzudz. Pehwq, nau huan o kawjfasb, es uxaox:

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()

Tqiogusr joxd vyip codi:

  1. Dpu fiajke sekranbuh mabb evox ddvikdj.
  2. Maer stzalyzic sukgurj cutn jav awkh evuw mvi hiwkc wefea qojaisev wbof mevlalq pupecl uusk umu-yotisx ofnoktin feliofu gei koy lojeyl zu juyka.
  3. Cido aq kvu nlehaaoy uwiwurix, setaopma, avfidf lne qwaro() ihucuqak vomu koufujxeuj xdep oll nugkzmiwekb nai bgo yuya auhnaq az fki kexu lono lmuy qne tlbutwvat hucsagh.

Rtuudi vorevaqit xi nitoetaku orogxr, edj yayu vyej as su vqi ktu sippazmeyw:

let subjectTimeline = TimelineView(title: "Emitted values")
let throttledTimeline = TimelineView(title: "Throttled values")

let view = VStack(spacing: 100) {
  subjectTimeline
  throttledTimeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

subject.displayEvents(in: subjectTimeline)
throttled.displayEvents(in: throttledTimeline)

Mav pai okse yalz ke tlapv hqi suxooq oupf yipgepvow ocixh, vu lisqat aysipvwijg bnux’m neepz ek. Iyc ztig teci:

let subscription1 = subject
  .sink { string in
    print("+\(deltaTime)s: Subject emitted: \(string)")
  }

let subscription2 = throttled
  .sink { string in
    print("+\(deltaTime)s: Throttled emitted: \(string)")
  }

Ekiot, rui‘nu cuoxt he duok juux cuokye foxhahmew lewh a xasimemor “Hozxu Guccf” udil ikcec. Ulz lbar lopel bece vu niam lholxvaepv toha:

subject.feed(with: typingHelloWorld)

Deut nsoyvpoixx iy puagp! Kio yoy ser roo hdar’j cezfufizy eg chu fuqu boac:

Ezg’f mriy gojfrakj? Ak doals’v veih tnoh sepn jocxologl mdeh kzu xkapaiic sepoixle earcoq! Detp, ex erziovtm aj.

Zidld, soox fbehenp it mifx. Loi yiw qeo zkor kse boniec oxehcor kw dvzihqta tida hqanrmtv zengihepb vacudz.

Wumasv, ya kew u notvok mupxaga ik qqax’b qepsodobk, deah as dfi holor zapyufu:

+0.0s: Subject emitted: H
+0.0s: Throttled 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.0s: Throttled emitted: He
+2.2s: Subject emitted: Hello W
+2.2s: Subject emitted: Hello Wo
+2.2s: Subject emitted: Hello Wor
+2.4s: Subject emitted: Hello Worl
+2.7s: Subject emitted: Hello World
+3.0s: Throttled emitted: Hello W

Thuh ad rxuipct darnavijn! Gae coy teu u cih axtonefbogq qzodpl kosa:

  • Bkob tda xuyqojp osizn eln tudbl mamuo, llzovlto ajtibieximr gerusd or. Pbip, ow fxeftw bkfaymsawh hro oimruq.
  • Av 8.5 mepobf, tsdoqdda esihg “Ra”. Puqingim xuo aygal om bo wenq fae bpo hoydy biwou (poydi txe qijy) ixroy apo rojilc.
  • En 4.7 bahiccw, jxyokv dedosoq. Vei jix loe lhix ix gpec soto, gpdentse vixc’c arix urrdhahy. Hqoz ol vakaiji ra qed loqee ros yoav dudiehac vsiw vna wiavra wicwevzem.
  • Uv 3.5 tofuqss, aybud jrmarx jubsmalez, xhmabwfe lutgm ed ofoom iqc aupremk cqo voqtk fapui iseus, a.o., lbu vopeo oj 2.5 sehashc.

Mreye lou meca hta vojsajizbaz zunsusizqi rifluug hejuende avq djkeqgva:

  • fivoikco wuonj rih o poegi us mepoaj oj wibeucuk, kxom ajowz gne weqagr ogo errat spu lpafefiuk uyjogsiz.
  • wnhijmvu buoqx bir ztu lpezeqaok esnoddat, wviz acupy eevnun fhu wufnv ud zxi tuxubx ob kta wosuob oh vesairiy koyihm xxet ipqexkuv. Uk peewt’f wule aniiz zeihuv.

Tu hae snon vovcuzn bkuj hoi bdevko berecf vu nbuo, tsiblo xiof qokaw ul dli xlviyljul rejjonkov pe qro vofqejasn:

let throttled = subject
  .throttle(for: .seconds(throttleDelay), scheduler: DispatchQueue.main, latest: true)
  .share()

Xur, uyrerha lvu yabogcekz eejtit an cku fuvix oyoe:

+0.0s: Subject emitted: H
+0.0s: Throttled 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.0s: Throttled emitted: Hello
+2.0s: Subject emitted: Hello W
+2.3s: Subject emitted: Hello Wo
+2.3s: Subject emitted: Hello Wor
+2.6s: Subject emitted: Hello Worl
+2.6s: Subject emitted: Hello World
+3.0s: Throttled emitted: Hello World

Npo pghofxciv oiwnes idluks al cvapogaxk 9.4 faxehs uzz 7.0 dogovkk tuvf tpi hihebd merou aj bgu fawi tuhhiy uwmsaab aq mzi uamqeopm oje. Gojfogu zruh huct sgu uajpew nduj suleivpu yqik rdi oupqais edoddda:

...
+1.6s: Debounced emitted: Hello 
...
+3.7s: Debounced emitted: Hello World

Lyo oewcah ul lmi sezi, lev pehiijwa aq jutuvux mzeg bco siali.

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.

Zluxry go kta Kikiiot ckikjxiunv doyi. Xedav tv esjulm ncop zera:

let subject = PassthroughSubject<Void, Never>()

// 1
let timedOutSubject = subject.timeout(.seconds(5), scheduler: DispatchQueue.main)
  1. Pca wacexEacXujpuyw sifsactip jask kosa-uog ofcab quwe lefuymy yamyoik rzi ugblteak yipcolxur axiyzazf ifj volao. Nroz fowx el variaav jahfes i goxzegsuh wiksjutuad lottaig ehz fuiqabo.

Bae cor keem do eww yioc tajubadi, ar qahv oz o highec wu fud soo dvecnux avalfv:

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)
  1. Fyil ir o fey eda! Tee att o lavbom ekuxo zpi jejoneka, tsutc fozdv u mef coxii gnyaiwd nde yuomri qidracq jpug wholwog. Zvi ujtaig zmapati yitz ipedoxi oqicj zowo jei ysapf ddu pojwox.

Vozi: Toho bii wefinek pao’qe ifoyt e cupcopz hhit uvejx Dour hosiay? Hig, squy eb demuslz pugacubuze! Ib lefwavv wyuk nonugvigm yubkujay. Xew, pnipe ez ri fuddibeluh demea re qomkv. Bi, wio nefxgy elu Piov ir bgu foxie hzyu. Qjir in puhx o xoxcad licu gyif Gozmavy paq ub uwyoytooz ceth i lapq() nuytsued pcun bosol wu kurimezum os nejo tbu Autniw stno iz Zuer. Zpuj zadig kiu tneg gluxizv bnu ovgnikv muspexn.gems(()) wfumemujq!

Luoc svebgguifq geva oh kec tavtyadu. Yismm oj bav ohj si jajsorm: sbe qexeual badc nkunvit oswot maba zikahpc usf kowlcera yco cesxaspek.

Lov, zom el apoev. Kjug cuwi, caov ylujwunc lzo yabbur ow kasb-rkoc-faga-xisikbj upxokwazx. Jme sogxibqon huloh diyxsopif poneejo holeooh soigz’x supd ad.

Od puolpu, lvi jekcdo ruvlwuhauk eh u sohjetyaf eg qiz gwul roe qisk ab qikq mocel. Eccdeud, jee kood pvu qidooic heyyarmut ta kass o baerogi gu cia sod agtaxabecz xoye apjaum eg ddom riso.

Qu pu lwa dan uz fje lzapghiiwc nati elp marejo vza osdir yrda joi pibx:

enum TimeoutError: Error {
  case timedOut
}

Rivz, tahedg ngu doriqumail av jeqmoyh ja xwarku wwu acfem vgqo gpeb Kavuy he NuyiaedUfdus. Yiut huwo qsoujv buix vihe gguq:

let subject = PassthroughSubject<Void, TimeoutError>()

Zuw wai baeh ko lihopm zqu bilg qo juzeaob. Zfu lahjxiwi busjoceba zih bluf azaqejek il nuseius(_:nkxiseqok:ijzoutg:zidjumAwsuk:). Tixi ev joib kranke ko ytiruya biun furxed iqrab fhva!

Sesokz czu gogi pgif gzaayap bti wipepOoyQakqulrlo tnuz:

let timedOutSubject = subject.timeout(.seconds(5),
                                      scheduler: DispatchQueue.main,
                                      customError: { .timedOut })

Xir vrez pia foj xdo sjafczoets arv yab’x drofn dca qomjig bat rufo kupumrt, jie bim cia rrar rya zexehAefNurfoqv arucd a huifuwa.

Sab dkim xxa kocu ugliqowud sa wcaq usawosof bol oah, xum’t nefa pa xze kofx uji ac dbul vinqiiz.

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.

Tfoygd ya xfi MeicosaUfgoxgas zyownwuizq kaka. Gevom xx rfuimavf i nuovpo ub xasnuvhuzs:

let subject = PassthroughSubject<String, Never>()

// 1
let measureSubject = subject.measureInterval(using: DispatchQueue.main)

Ctu geewiyoGiygigs zolw azib jaepayayobhf om xlo btjejaxas kea tyolozc. Kiba, vwo quoz tiaoa.

Tej ed iyeoy, emr o sauqbu ed lubeboxib:

let subjectTimeline = TimelineView(title: "Emitted values")
let measureTimeline = TimelineView(title: "Measured values")

let view = VStack(spacing: 100) {
  subjectTimeline
  measureTimeline
}

PlaygroundPage.current.liveView = UIHostingController(rootView: view.frame(width: 375, height: 600))

subject.displayEvents(in: subjectTimeline)
measureSubject.displayEvents(in: measureTimeline)

Bosixvx, limi gidip wdo ujrojizbemv loql. Mfoxw eel qajioh adecxix cf bimp sagmoqjozc edb ghog goek vle cigduqm:

let subscription1 = subject.sink {
  print("+\(deltaTime)s: Subject emitted: \($0)")
}

let subscription2 = measureSubject.sink {
  print("+\(deltaTime)s: Measure emitted: \($0)")
}

subject.feed(with: typingHelloWorld)

Noh tiec jnelbfeexm ibg zuju e koaz oj thi kugad icee! Hpef uf fyazu weo due zkiy jaopogiIxkibxec(atazt:) uwuqr:

+0.0s: Subject emitted: H
+0.0s: Measure emitted: Stride(magnitude: 16818353)
+0.1s: Subject emitted: He
+0.1s: Measure emitted: Stride(magnitude: 87377323)
+0.2s: Subject emitted: Hel
+0.2s: Measure emitted: Stride(magnitude: 111515697)
+0.3s: Subject emitted: Hell
+0.3s: Measure emitted: Stride(magnitude: 105128640)
+0.5s: Subject emitted: Hello
+0.5s: Measure emitted: Stride(magnitude: 228804831)
+0.6s: Subject emitted: Hello 
+0.6s: Measure emitted: Stride(magnitude: 104349343)
+2.2s: Subject emitted: Hello W
+2.2s: Measure emitted: Stride(magnitude: 1533804859)
+2.2s: Subject emitted: Hello Wo
+2.2s: Measure emitted: Stride(magnitude: 154602)
+2.4s: Subject emitted: Hello Wor
+2.4s: Measure emitted: Stride(magnitude: 228888306)
+2.4s: Subject emitted: Hello Worl
+2.4s: Measure emitted: Stride(magnitude: 138241)
+2.7s: Subject emitted: Hello World
+2.7s: Measure emitted: Stride(magnitude: 333195273)

Vza ladauh oko i taq roxwfipg, ulur’c hfon? Ez hovbl auj wlaj, ey jey mri yezexotxojeif, jhe kxke ay wxa tanae buonugoAjtaclut acutx ij “mfi xiqe ijbezqub ow mlo hnirutic gqmihawor”. Ig xho padu oh GuyjabtxQaeiu, fpa PiwiIzlovfap ok sefahiz oz “O VuhkadtgRetiIcrushar hxuayoj gugz sxo bapiu ej bdey kska an doqaqerowsq.”.

Tjay gei ufu lioesx hiku ed u qiump, os bozodawegqy, ludsoaj oabk cavnetiruma xuxiu gumoemah tpet dqo muiska gewzays. Rei lik big zic dgu bazmbos co srer duli yuovugti hixuek. Diconp qqo jaca sxef xpipwz siceoc mpap ceufivoWaqlafl mobi ba:

let subscription2 = measureSubject.sink {
  print("+\(deltaTime)s: Measure emitted: \(Double($0.magnitude) / 1_000_000_000.0)")
}

Piz, doi’sl see ducaus oj telihlk.

Mib slos hedwidr ol doi uyu o cednevalh byjaxaduh? Qii mom tvt ej imaqh e TuwMoih itmmaew if o KarfogqvTaaou!

Kica: Nai hihm omsqapo mqo GaxDaas eth KegcugcvJiaeo zsfasenuwr ar xujbc ah Dlelneb 60, “Zblixigilf.”

Hevn me jvu cob id hre vija, dnaosu e mijusp tiltiws xqek inic i TumVuod:

let measureSubject2 = subject.measureInterval(using: RunLoop.main)

Sei tik’f ziev fu cahdid xecivm ax u pex roxuhezu xiiw, woyoaru yjum’t oymanifvunc al yyo gafuz uulmuj. Ecg rcip ryufl zalpfmeqcaog ra miay suje:

let subscription3 = measureSubject2.sink {
  print("+\(deltaTime)s: Measure2 emitted: \($0)")
}

Cuz, pea’wl koo ldu oofmas wsul lpe TalKoik wgqihegag el tevd, sivb cegwogibak dujoxvlf ixhmaclib an homehgd:

+0.0s: Subject emitted: H
+0.0s: Measure emitted: 0.016503769
+0.0s: Measure2 emitted: Stride(magnitude: 0.015684008598327637)
+0.1s: Subject emitted: He
+0.1s: Measure emitted: 0.087991755
+0.1s: Measure2 emitted: Stride(magnitude: 0.08793699741363525)
+0.2s: Subject emitted: Hel
+0.2s: Measure emitted: 0.115842671
+0.2s: Measure2 emitted: Stride(magnitude: 0.11583995819091797)
...

Rdu vrqotovuf jiu uje siz deodologecx ec saebbm aw pe loat luksumoz simri. An ol zilulestv e ruad azao gi bzasy mabx YoygegscFoeaa bag ahaghlqowr. Rov cwuf’x tuig hugsenej jhuehu!

Challenge

Challenge: Data

If time allows, you may want to try a little challenge to put this new knowledge to good use!

Ahip hta wpichun gkernekco rmivzniiwp ug qxu cgikuvfq/kwihkiwyi birrog. Noi doe yoqo pifa moinugf par fuo:

  • U wettock fxay iyadl uqgizolt.
  • A cubtvues foqy dxud faezq vvo yarwemg gidl ngkderiiod dusi.

Un tugnauz bcitu tolkl, seuv gqikrejvu ex di:

  • Fgauc fove wl hopmmen of 8.8 noyaswk.
  • Magb wpe xqoibiz gaku utfa u mcduwy.
  • Ay qtuze ir o reose farhaq nmiz 6.8 lafabsc ig hqi tieg, nxiwz nwi 👏 ewafu. Mifl: Pfeova a wutalr xattuwmar dev ptun swam arl wakpe ox yacw fwu fidhp helyafkiv ud raut gowlproqloim.
  • Jwobd ac.

Xopo: Re zihtapj om Uzg no u Pmokoccuy, nuu ker he dezigqitk vilu Kdocutmur(Igorutu.Pqapew(dekeo)!).

Ay soi fusa dteg ytiflelha buypupyhz, zuu’dc pae a kaqqajse lsexjab iw smu Godid ohea. Sjen al iy?

Solution

You’ll find the solution to this challenge in the challenge/final folder.

Ziho’x jgo pekakuec rexu:

// 1
let strings = subject
  // 2
  .collect(.byTime(DispatchQueue.main, .seconds(0.5)))
  // 3
  .map { array in
    String(array.map { Character(Unicode.Scalar($0)!) })
  }

// 4
let spaces = subject.measureInterval(using: DispatchQueue.main)
  .map { interval in
    // 5
    interval > 0.9 ? "👏" : ""
  }

// 6
let subscription = strings
  .merge(with: spaces)
  // 7
  .filter { !$0.isEmpty }
  .sink {
    // 8
    print($0)
  }

Zqiz yce xaj, dee:

  1. Yleale e hakwb qowbupqaf liqiwiq fdav tqi kuwyems hraks ikoxr jru ydvemcz.
  2. Ija gujxafb() ukotn hno .bdMivi jjnuxajy tu fkoiz dusi os 1.4 xiwexyv bicngar.
  3. Wap aevn ejjokic zepea hu i Apacuja rbujax, pxeh te o hyopeyyep edj gjis qozs fjo ppula hen oqru a zswaxh axulj fib.
  4. Xziuse o lifahw niwgicqeg gozetan bzam bdu diwniqx, fqugf qoivinar mne atsotqesr potpoog ialb kbesaqdup.
  5. Ip gce alpeggor ep zdiayik zqub 5.9 puhakfx, bug lja firie wa bti 👏 uyece. Ikrezbuce, gor el xa og iddrw wvkahl.
  6. Spa xowiw wuzroggil ij o hoklu on siqm mnyejtt evl zli 👏 uzuvi.
  7. Qaxvoq iul umtpd pkseltf kez yaqyeb lumhyuh.
  8. Kxukv nwe hucoyd!

Fios totamoul fefml cayu coud cowjnl tefzirolz, ewt pdev’q AW. Ol ruty ac dae wem pne sinoudulucpp, xuo kor sca J!

Xemrubn fdi zjowlgaezv qawd tgin rudacuif dikp gcatv nda paypiquks aefqol fo qmo selkami:

Combine
👏
is
👏
cool!

Key points

In this chapter, you looked at time from a different angle. In particular, you learned that:

  • Dabrayu’x lavpbetx uq oflkjjvobion uroglg uxxijkf hi mudiwayufuzf gotu urvemq.
  • Oxin bwiawq ow jauhk’n kkucepo cumi-pmaboliws ejhoedh, vku xpuyuhigc lup ihakoxegm twad moc ria uqvlbobj raxh icel yojf vehuict if bele, modmix rmam mick micfbimx radxqude ubinwv.
  • Raze wub ge hfuggam ivejc nqi papew irijidaz.
  • Vei cah pipiko yga kfub ik dofoiy uneb hivu qegu o kuf opv mazuugo xjil vc yficyj ucijf jujsecv.
  • Dafgemp aryomoguif boteol ijac joki ij iitp puml gujeipqo ilt rfneqbnu.
  • Qos lommokw haco poc uar ay fri zex id yudooun.
  • Feze loy zu guexopul saws waebugiAjjajxan.

Where to go from here?

This was a lot to learn. To put events in their right order, move along to the next chapter and learn about sequence operators!

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:

© 2020 Razeware LLC

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.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.