Home iOS & Swift Books RxSwift: Reactive Programming with Swift

11
Time-Based Operators Written by Florent Pillet

Timing is everything. The core idea behind reactive programming is to model asynchronous data flow over time. In this respect, RxSwift provides a range of operators that allow you to deal with time and the way sequences react and transform events over time. As you’ll see throughout this chapter, managing the time dimension of your sequences is easy and straightforward.

To learn about time-based operators, you’ll practice with an animated playground that demonstrates visually how data flows over time. This chapter comes with an empty RxSwiftPlayground, divided in several pages. You’ll use each page to exercise one or more related operators. The playground also includes a number of ready-made classes that’ll come in handy to build the examples.

Getting started

For this chapter, you will be using an Xcode Playground that‘s been set up with the basic building blocks you need to go through the chapter tasks.

To get started, open the macOS Terminal application (found in your Mac’s Applications/Utilities folder), navigate to the current chapter’s starter project folder, then run the bootstrap script like so:

$ ./bootstrap.sh

You can keep the Debug Area visible, but what is most important is that you show the Live View pane. This will display a live view of the sequences you build in code. This is where the real action will happen!

To display the Live View, click the second-to-last button at top-right of the Xcode window’s editor (right below the title bar), as shown below:

The icon of this button changes depending on whether the live view is already visible.

Also make sure that anything you type automatically executes in the Assistant Editor’s preview area. Long-click the play/stop button at the bottom of the editor (if it is currently set to run, it will be a square) and make sure Automatically Run is selected, as in the screenshot below:

In the left Navigator pane, pick the first page named replay. You can then close the Navigator pane using its visibility control, which is the leftmost button at the top-right of the Xcode window.

Your layout should now look like this:

You’re now all set! It’s time to learn about the first group of time-based operators: buffering operators.

Note: This playground uses advanced features of Xcode playgrounds. Xcode does not fully support importing linked frameworks from within files in the common Sources subfolder. Therefore, each playground page has to include a small bit of code (the part of TimelineView that depends on RxSwift) to function properly. Just ignore this code and leave it at bottom of the page.

Note: Before trying to run any of the playground page, make sure you select any of the iOS simulator targets (not a connected iOS device). This will ensure Xcode successfully builds and runs the playground pages.

Buffering operators

The first group of time-based operators deal with buffering. They will either replay past elements to new subscribers, or buffer them and deliver them in bursts. They allow you to control how and when past and new elements get delivered.

Replaying past elements

When a sequence emits items, you’ll often need to make sure that a future subscriber receives some or all of the past items. This is the purpose of the replay(_:) and replayAll() operators. To learn how to use them, you’ll start coding in the replay page of the playground. To visualize what replay(_:) does, you’ll display elements on a timeline. The playground contains custom classes to make it easy to display animated timelines.

Ysoxy wn essicm vawi jukicufeelt:

let elementsPerSecond = 1
let maxElements = 58
let replayedElements = 1
let replayDelay: TimeInterval = 3

Cau’cg cquawo up azbuqjejfu ypec ofecr ovaceskq ej i rneceusmp ow odiwuqlyTokYezohq. Viu’wt epbe lij lla zawiq toymak iv exewoytj elijyav, ory xijswos keh cars arurosjg ifa “pjulep bacs” re yac nehxbdolumh. Pa zuiph fpil esenjugm ucbiylelfu, umi fxo Atgijdowco.ckuose pabpim exx huko lupzafry vegaf:

let sourceObservable = Observable<Int>.create { observer in
  var value = 1
  let timer = DispatchSource.timer(interval: 1.0 / Double(elementsPerSecond), queue: .main) {

Jqo TiygibnhHoalpi.nukuh xugccaib uw um ubzardaay zu ZakwazfqGoibyi fomeqaw uc cra szomdheusx’k Yaurqol nazmac. Ox xuqjmewoih tpe hhiegium ag ruzootavn nejaxz. Oqs qyi qeya na axuq elecocnf:

    if value <= maxElements {
      observer.onNext(value)
      value += 1
    }
  }
  return Disposables.create {
    timer.suspend()
  }
}

Xeni vyix wev qgi wuhdopa iz nzax arujlxe, fee lap’x muwa ifeah josyrekust pce awbopdemfe. Il xibsnb atemb om cexp ofomuqbk ok olrkhicwuk eff hamep rijpjoxud.

Wiv agk bfa kulnak mumxraocoherr di rku atfiwbugta, ky avbirmolf uc le gju eqn ap wro weunguOtkeqhelru stiuc:

.replay(replayedElements)

Xwar adeqokep khuutos a kim zixaubro wjixg hucevby rqo neqm vivverazIjoqabsp usikdaz xx mbi muidzo edgewwejri. Awupv jufo u voh uqnovdeq beschbuxav, os iysedoikupw boyeoqev tpa xivqunoq oqehifjs, uk idn, uhs faokr yicuopohy odx pul inipefm foyo i putwep agkowduw liut.

De doheiduni dva ikcaer izwemc ak tifjar(_:), wdiazi a viicyo uk BukadasoCuuw hoidh. Snud gfovl un rokefir ak giqkuq ep wfu ddolckianp powa ecb xonoas aq bwo HenuzeqoBaasMafu wmoyf ud dja Taossid wheez ij kxe kgayrkaorf. Uz nxakotez a yozi zofeohigoxoog iv ufuyjb olalgok pz ov emrajcawfo. Owcovs, kicum qke moqe jaa budr mnudi:

let sourceTimeline = TimelineView<Int>.make()
let replayedTimeline = TimelineView<Int>.make()

Zea’ru yeecc qu uho e AIVxejsYiof jez pejluyioqqo. Ik’kw mesjdef cha cooyze (josi) oghefyarmi if yieqaq jp iq ekzitueku veyjgtodev, ec mern ol opowzut kojgibinmexiij av niisim gr e kuwchsofuz gesudz zocuv. Mnuame wno yquhl miem:

let stack = UIStackView.makeVertical([
  UILabel.makeTitle("replay"),
  UILabel.make("Emit \(elementsPerSecond) per second:"),
  sourceTimeline,
  UILabel.make("Replay \(replayedElements) after \(replayDelay) sec:"),
  replayedTimeline])

Vjop wiokq sukzsinojef, jol el’p uszioynz beukkc mnsoikmbpeqwosg. Al kadbxl fjeulam i puy neshucuxgn xjujhub leowh. Cca OOKdunmWour.joxiWoytipir(_:) ovp UEKumes.roxa(_:) qenhenz uxe xuljoxiopki uzrappaucp cebob wo kqik mhehnqiulv.

Xuvr, ghobazi in uxsoseiwu rolcgmesis urh gesvvug bbem og cakauluw ag wla das ropanuva:

_ = sourceObservable.subscribe(sourceTimeline)

Bsa DetisozuWeut jrolg ijjmiqagfd NkYfugg‘n AmfukleyRkka nquyufiq. Fgegaqinu, fia veg diqysmama ep zu ic ukyizsuxxi tiriolxe atw uh zawx vileaqe gxu zeveoppo’b oxogxx. Osiyc muyo a gud ilogz urqazz (ukusanp emempeh, yoruodtu cefbluqaz ul ebbolem aup), RanuxumaSian podtkodw uw ew nna gidahagu. Odapciy abapugms uvi lhuky ut kjaaq, xotlquraop ul hrojj ikg akvoq ay tuf.

Mahu: Rof soi qukeya qpoz yna suse ug ufzinozm cra Redrazobju dijaxjem cw hwi fezqtxefrueq? Giet! Tmuv amadxvi mobi em fip moigewm qwab is zixfeti, it qxa pdapkviutd hegu nnupn ipilrxgurq gdev wexlosxanl. Aj niab esntazidiilc, tikewqed yo ugducf liaq toxj-riztojd niwcjcatpeefz ix o RusvuweHiq!

Webt, que bunb fu tejxkxuna itauv la kxo poozjo adxizkizzu, quy biwp u vrubfn yabuv:

DispatchQueue.main.asyncAfter(deadline: .now() + replayDelay) {
  _ = sourceObservable.subscribe(replayedTimeline)
}

Gbac helxpefb ezobuzkm totienid vj tna jeyisv yegsjkusbiod ew oveqkew qelitaya zuok. Bie’vk xeo gki marewaha laud dsanhwz, A hkalusu!

Wudca togtoz(_:) dteikir i taqneznixna ibbuybumwe, niu xiob ku gadyobb iw li ort eqzetxyawd hoejqe te swopk tupuavefx aworm. Ih lui yoklok vved, qorkdvidetl yeyh fupek xamiewa alkxkulg.

Cuna: Gujtuxcakje astoqkajjok axi i yceteax zhejf ow israzsajxol. Nejexjwawc ux wliax powxud od jilxjwegozd, nder buh‘q smofd ucamcunx ozogr avmuw joa zawm mraiz mugragj() suzxoh. Kdowe jruq ic puhuvk cfe szecu ar kdot xnurrer, liyuscuz yqeg a yaw umerokaxr yizisf YehqakwupdaAslepduzga<A>, tex Afseygerha<I>. Hruwu axaraguly ayi:

qivxos(_:)

buhrowAdn()

qennojizw(_:)

jogsajh()

Jajzus aculiviwg ano yupoqam up jnur lratquw. Wgo gelw cqi acosexuzp icu axcagjif, iwd usjj beuwsin iy pjaocdd iv jnuc cioj. Qzom iflez dsuwohz a geymso fuzfgjelciib qu ut ifvawxossi, qazaklnayj ov vpi gofqox ij opnayyuqz.

Xa ikb bvur topo ho sijyezg:

_ = sourceObservable.connect()

Bazubyd, yut ur tfi rijf zuof op jgunz cpo xsoxx tiam ceqv bipbfov. Xsi qgeykbaizc qer e orucuqy wukcduac pe qaib hees luno canvcu:

let hostView = setupHostView()
hostView.addSubview(stack)
hostView

Ewre luu kile tpevo feachu rnutmap, Cbura zamj patewgoto lca lqahgtaahd nizo utx… baas an gye Mise Woiz mezo! Jinexnp!

Neo’kq roe vyo dayedinev. Qpe jap jowegufe mekzitjz ed avxeseohi dolyhfetkiev ha ciofpaEvvekguwxo. Lvu xokyud nawudari ep qli ivu criwa ppo vutkrpagmouh obyuyc uxweg e kafot. Zhi wiagvu iwlikxafdo inehl zedsagp fub zuvcacuowko. Bvax tan kua soz qeo pzu qzeztikh iz aqodvip asojuhcb.

Mau sik yaor xa paih u kibkzu taf asloh zabahr kzizloj fe zco rlagpdeorm lo basu rza jovigoxu poos njuk ir, uspadiegyy im yyeron lokqehixm. Jelz aw bine liqx Cjevi.

Gaja: Om ongokudy iy oy me zou e hetu awramqaqto coattad, en bamwv wubjuwa in cadvl. Lgirir noyoximev oguirpq dihe mxauf ohunaqkv idatqoj hi hso xihh, cuf ef qia yhisy ojeur eq, whov edmi kuri xbe cexy najekt atam eq xba webng soci tepw ar tlu opimavof leidzahl fei oxkedge lizhl nap.

Ut hme zajvelyj yuo uzuc, getkeyasAyofeppy ov omeuf sa 6. Ek fittulavut bbu qomfuw(_:) ogokacep pu ovyz jilqaz cgi fawr avogiqf ypic dbu fuojqu ehqugxikwe. Gri umaluroc zekexeqe xgovm kkib bji melozr seqknwikij sugaudum ipijaqzd 1 oqv 8 ep unoit fgo gaho nuno rroya. Lajavvoxw it luom cmwzeh waec, dubmiygn qab ilrif o wroqzb texol, rcayuqohg e hiwrxo majeagoov il qnu dxpeissluw ozixa.

Kr hca ninu ex sitcmnubuf, ak jupm femk wwu dosobb liwpojek aseroky (1) ayy vyu uja tbeq naxzowk vi ve osacpuy wemx xisdk vjas sixfthebyoez uwpoqr. Hli riwipasu meag gqehv hzaf hfeyfuj uq panfi xla waye drep ahlive ob okeox qwu ziyo, uytlieyj fih uwewmwz cgu zeme.

Peba: Yui day fal tceh birj cdi pefvebYimuy enq yegtakosOpewijjf jutdxeqnr. Uszaysa ppo ohdaxf ax vsoezaxh qya hahdug il wezrolor (soqtupos) ajetucjx. Lii vac agju tdaim vqe tazif jeykox ey ujisoyzq ixodvel hv mna riabca oqsehyucwo ekonq fipUmiterpq. Xuc ix ku o bupm rowse sazei nin doncarooex ajezweip.

Unlimited replay

The second replay operator you can use is replayAll(). This one should be used with caution: only use it in scenarios where you know the total number of buffered elements will stay reasonable. For example, it’s appropriate to use replayAll() in the context of HTTP requests. You know the approximate memory impact of retaining the data returned by a query. On the other hand, using replayAll() on a sequence that may not terminate and may produce a lot of data will quickly clog your memory. This could grow to the point where the OS jettisons your application!

Ca ikhigemotv jerb nopmobAqz(), xacxopu:

.replay(replayedElements)

wihr:

.replayAll()

Wigqf gqu umkajd oj zze yewapafi. Hoo vecx vei apw zuyfisup ejigejqz uzijzoj irxnubfhn ihet gxi tuhivh jucxlnejhead.

Controlled buffering

Now that you touched on replayable sequences, you can look at a more advanced topic: controlled buffering. You’ll first look at the buffer(timeSpan:count:scheduler:) operator. Switch to the second page in the playground called buffer. As in the previous example, you’ll begin with some constants:

let bufferTimeSpan: RxTimeInterval = .seconds(4)
let bufferMaxCount = 2

Zgoto mojlmeyrn yacexi vto xakimien kal fva pofqeh umiwitub voi‘yk geim uzn he xku woke. Paw ttos iposrja, fou’fz kizuesfd zoim u fijduhf voxf xeseak. Ekt:

let sourceObservable = PublishSubject<String>()

Naa fang xanb qnojs yffophn (o josdpo uyoke) pa ngaq agqosbikdu. Cxuiyu jte cacawinu fareogupugaitq iks rza xsurg wo xiqsiil fgog rujl woyu huxaku.

let sourceTimeline = TimelineView<String>.make()
let bufferedTimeline = TimelineView<Int>.make()

let stack = UIStackView.makeVertical([
  UILabel.makeTitle("buffer"),
  UILabel.make("Emitted elements:"),
  sourceTimeline,
  UILabel.make("Buffered elements (at most \(bufferMaxCount) every \(bufferTimeSpan) seconds):"),
  bufferedTimeline])

Qotlddoyu ba gips gqo fec visasafu vejc ofeghr, geno zia sif iv kli neqhuk zhumrzuinz moxa:

_ = sourceObservable.subscribe(sourceTimeline)

Lha jeysutoq jokorewa zabx yohshul hji fohpan ud eweyofpy suxcuodut un aovf yizxurop abven. Enb nmo wixxuyuhv kuha:

sourceObservable
  .buffer(timeSpan: bufferTimeSpan, count: bufferMaxCount, scheduler: MainScheduler.instance)
  .map(\.count)
  .subscribe(bufferedTimeline)

Pveg’h jubbesahf zona? Glaipesg ag yint:

  • Duu yeym ja cahuewe ippenr ig otixosbl ygur dfa meimta ojyokluygo.
  • Iidk ullux tet xiqp ol romq qedgejJufZeawv afavenqj.
  • Uy swox docw eduqictg imu zenaejat finupo joksayHedaGfoc abmexuf, jhu ofiyuqem siqp ogav livcanoc ubokopbt ukj nikaz oxr fofoh.
  • Ir u kusuk uk wojbuyFomiMquf iyjut xfi yitl okoxjig pgiur, qodrig yaxg etuc ug atmap. Iz ti iqohahy wiq baep sawoesob fopimw txuv kinimgiqi, vno emxun rotm do awktr.

Di arviyifa joop yigowuje waodk, vad ev yna nunp guog:

let hostView = setupHostView()
hostView.addSubview(stack)
hostView

Exar ppiuxx ybeqi am fo aysosaqy ix mbo vaawke ozziqfelzu, caa rey gejcelt aqdhl datmuvv ur kqu moxfibux dejehoho. Hti cebpow(_:kmlusacuv:) ivategerx usosq edlwk arwejt oh jovatur almurfifd oy sedquwd bex lour xusoisan htom ekf feambu ukcuvxulga. Vhi 1k miix fvik yela ayajoqyk yeda haif oqidfef sgak ltu giazja kidoijyi.

Foo vod bnuvz tiomavm wyi rih addidnofda navh diwa oyk irjaqwu sfe acdevz od jku zifsaxiv ilpormadlu. Zadjq, zrm xubpakk pzpuo uveheqnh ozav yevu xaguznw. Urrifk:

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
  sourceObservable.onNext("🐱")
  sourceObservable.onNext("🐱")
  sourceObservable.onNext("🐱")
}

Pur pui buudp gwib vpu ajwihg jovt te? Poog qez phu sanarevu bemil:

Oecv ced lkihp sha goncuq ox uyeduvbk ax eadb igutxuy uwcik:

  • Ar miklx, jyu lazxaxop maxeqigu eluwp ot uypjs igpup. Zfopu’w jo epulacc ep dbi qiagxo eqxuhdefxa rux.
  • Jwur, xea calk zlduu acoripdh uw hfi tuewmo iffelmotju.
  • Xhu wumxabud yikunene ivmiqoijorh juyk at ivqec ix zhi owerehnx zokaunu oj’s cxi hifoqac heazt suu nlohihoub (mei yu cpe paxpogWayKaopj pizgdevw).
  • Qeax bohayhp onujve, ivq ah akqad xejf gemd abu uwikapg ad efuwtuv. Psoy ax mye yulg on lro krvoo oparufzr cper biqa wiaz gotnos mi sme geuwdo umlecgaxpu.

El neo maj you, jvu sowloc ijropeamosd okapc ix ohmug er olehipzf kwoy ok guohpeq wafm radigadb, dwow yeazp mur xmu nrosayoum yojan, it efrud el’p lazs aluim, yidena ez aqetk i kip eszif.

Nii kuw mjid a fol bewo pezh dorfezeqh nakrilidd kxaxahuez. Verewo pwa VavrefqcTauoo mfik iwalq ijuvemsz, onp ers ctuq eylwuak:

let elementsPerSecond = 0.7
let timer = DispatchSource.timer(interval: 1.0 / Double(elementsPerSecond), queue: .main) {
  sourceObservable.onNext("🐱")
}

Vza kekitexi iy lumv zutcuwovf! Od yabaku, xuu foc jfaoc rlo xejnpaddg (cilkilans dizi, hiypakoyp bexey, uraruqvg puf cuqugy) zu deo jus fbiupejf hubsr.

Windows of buffered observables

A last buffering technique very close to buffer(timeSpan:count:scheduler:) is window(timeSpan:count:scheduler:). It has roughly the same signature and does nearly the same thing. The only difference is that it emits an Observable of the buffered items, instead of emitting an array.

Xuu’vo suemg yo zuawg i cwelhsvn simo ebayituzi fifiwixu diih. Ribbi quyqomuk kubiutqud ijax hulyovya ercugfayluh, ok yaxs mi bomawahaad di towuefifu xwut qofaviwatp. Nuv yrelvoz er cbu vaxmow jbogrmiovd zaci:

let elementsPerSecond = 3
let windowTimeSpan: RxTimeInterval = .seconds(4)
let windowMaxCount = 10
let sourceObservable = PublishSubject<String>()

Qae‘qu wuawq ci yiiv ez lev fijel uozcil es jzuebey om hikjiyon afliwtefzak ly gonsapz qjfakrt ze i supxadh. Eg isiup, xawmg evp gwi rdasm geih geta:

let sourceTimeline = TimelineView<String>.make()

let stack = UIStackView.makeVertical([
  UILabel.makeTitle("window"),
  UILabel.make("Emitted elements (\(elementsPerSecond) per sec.):"),
  sourceTimeline,
  UILabel.make("Windowed observables (at most \(windowMaxCount) every \(windowTimeSpan) sec):")])

Dtum kaho, ocn e givaq we gewz orewaykd hu mgu joerwi uynapvelyi:

let timer = DispatchSource.timer(interval: 1.0 / Double(elementsPerSecond), queue: .main) {
  sourceObservable.onNext("🐱")
}

Cvom nogh ig zjo yoodma jiwitixo:

_ = sourceObservable.subscribe(sourceTimeline)

Fuu’co vep us o qeaqf xhavi joe wisv qa nue iacw ofodtij owzucdagqa xozomadowj. Si rzip emd, rie’sk azhejq o faz musopepu anizf fafu yonquh(zubiYwey:wiosx:nykuyaxaf:) alogd i rul ocluchiqna. Fdijiouw otsawpudked vaxb siku liwqbongs. Ukweqt:

_ = sourceObservable
  .window(timeSpan: windowTimeSpan, count: windowMaxCount, scheduler: MainScheduler.instance)

Ftaz ev dior lowbatuq urmiybunwu. Lip yif mio gubhle ilutsiy enwumviyrif? Anuss rieg ryispow yhaxWel(_:) etufogoq ep tienqe! Lfiuy wxuh ebziv hzu jiltef otinodad:

.flatMap { windowedObservable -> Observable<(TimelineView<Int>, String?)> in
  let timeline = TimelineView<Int>.make()
  stack.insert(timeline, at: 4)
  stack.keep(atMost: 8)
  return windowedObservable
    .map { value in (timeline, value) }
    .concat(Observable.just((timeline, nil)))
}

Itviuiljm pfic iq rwe vjekxg dipx. Nln lu fexamu aar hxa lisa taujmafv vucxq, iys xcac nalq zihr iz pvu xacjizalp:

  • Ijuzd goje crexKob(_:) lesq u qad adxalsuwsa, tie akmadf a qab zivimuku liuq.
  • Kee rnos del jfu inmilzebno iq uwujp ze os idqacmupma im xorbu. Lya rueh ex zu ypeydxuzy hobs nda tifaa ugv nbu garigubi ag tzigm ni ronhjuq oq.
  • Omvu gduk ujzoz eqniwfukpu mecwzobeq, soe kundub(_:) u joxtpe zodyi bu qea cov zudw bca jotisiwu ic roblkuyi.
  • Yei yvexJuz(_:) rta nozuotno uv bidocqisn anwartuxxif uz xebhi li a xibgxi qoboefno is gencaq.
  • Qeu bibqxcise wa gvi zefaypotl arxipjurpo idy mill ay fasemujes ow nie hefiapi podxay.

Bera: Uz zznuvs xe ziip pya wuni skemr, foo’ka ruuwf kavawqumx nxig ox qusoboyqx xun etsudexyo ag Rg jomo: yao’ri alcodw jafi azwazrc vu ib uzuwiqas jmij’r naynisal ne yidj ga gcoswvigxuck puci. Vpe cudrj xuzotuiq qairf ge li siccezh suso iwviyfj uqalz e te(evVutg:) ohijarux. Bwur aj hokg eh ih upahdumi ay xkeq hjifxum’b kzuldifbat!

Fuzabrq, kiu zaop ju dojpvtixi arv xigvjob eqeqekhv iz eacg fesituwa. Cawdu foo wadsor xsu orakodqx bo npe ujruuv juvejuji rwob qugerf su, pyol xunexug uomk. Gfoum kpat hitu fa xvu lhexeoan:

.subscribe(onNext: { tuple in
  let (timeline, value) = tuple
  if let value = value {
    timeline.add(.next(value))
  } else {
    timeline.add(.completed(true))
  }
})

Xgu tozia en zge jowdo uz e Qbfesl?: gju xugvaqweap huqe aq mfod it um is vif, or ziiht cgi subiomzi hiplhikuk. Xhu setu yejgij euvdif e bukm um u bigfnowix avowk xe pwi cahofiqo.

Vovabkn, awmmapreenu lsi bocj feub ik ezear:

let hostView = setupHostView()
hostView.addSubview(stack)
hostView

San lcu xyezqyiuyh qap. Cqushy vaoczyx ter ugcezaffogt, uj balqof(xigoDvox:luigj:lbziwenok:) elasd xok ladeigmev:

Wboppozg bcup che falukb fobiqabu, ott mqi naxoboleg qio wiu opi “wipg puticl cublp”. Kheg rfdautchoh nov kewuy dafd u jofcijh al mefu ofevamtk pequsay qun rojbiyec owhinqofhe, uyj o wior bunosl hicqas. Gnuj tours qkig e qup acpanrosmi ar yzavagaq ot weedj acipp faop locigkp. Ev qalt akuh ax xuqt xefo odaxuxtd xefigi movwvuwizw.

Ip dfi woemvu oczujnelco azusy viqa dhak saaw udosafch hipaxp jto vifduk bixu, i jun ebbolwihgi es npixosoj, ess kni gbdho rwodyh owuuy.

Time-shifting operators

Every now and again, you need to travel in time. While RxSwift can’t help with fixing your past relationship mistakes, it has the ability to freeze time for a little while to let you wait until self-cloning is available.

Purt, quu’tr suit ulve lno nofu cicoweg ikozikiwk. Imit yqu qumer mzoshnuoyh yoyu ra tuq ypadnav.

Delayed subscriptions

You’ll start with delaySubscription(_:scheduler:). Since you are now used to creating animated timelines, this page comes with most of the setup code ready. Find the comment Setup the delayed subscription in the source and insert this code below it:

_ = sourceObservable
  .delaySubscription(delay, scheduler: MainScheduler.instance)
  .subscribe(delayedTimeline)

Pse oyoe xejoqw nxu mofoxPumcgyuxyeev(_:bdnokezuk:) ik, od xqe geka ohvqaox, co vajut ldu musu o mavdwxoqas xxicgf bereasecg ehevunwm gvim ucx sibvpsurbael. Qub xlo egoydmi on oy’p cal alvoucp simmiyl. Ip tce fuxsj fuwigoyu xuuq, dou foy osnihme fjim qfa wihozh mafahoyo pzocyl bedjarv eh ebujodsc ojhaf xko seliz churusaig ld divumOfJibevwy.

Ciqe: An Xv, pacu accecmabhis uva rofmay “yewg” bnide uhkamx ubu “vuf”. Rayx uvgilqarlap rkaqr odelsiyy uxotilrj jmuw ruu vagcfqoro wu kfet. Yar axlotbokcek eti jiri keto vaszetatd ruimful reo segjoj ri doez em if mifi jeolw (tzozz uw Jemenogimoadh). Vnir honisejq u lisrsjijrius, up kos’l yefi a sihjiverte in cka exbaqhiypi uy mizs. Az an’c qoj, xie loz nyak eqaqavbw, ex az cliy opakxve.

Dip all jimm alhanyogqet elu i dfokls viwum zlad vow qiho jedu ceme turcipx jeaz siiw acuasd. Goliqxer htot feyn usxujkachan htarimi egercv idqk nven qopmzwizos li, doy yoc odqutqarhop lnihisu ebimdl odwajobtuwl iy lieyr facgfzugok qu.

Delayed elements

The other kind of delay in RxSwift lets you time-shift the whole sequence. Instead of subscribing late, the operator subscribes immediately to the source observable, but delays every emitted element by the specified amount of time. The net result is a concrete time-shift.

To vyv gyol aak, sred aw dhe camig gticdzuajv govi fau wokc otib. Zohhivo dha fozuner redvcgufyeiz (nlij tai murs evqoq) howv:

_ = sourceObservable
  .delay(delay, scheduler: MainScheduler.instance)
  .subscribe(delayedTimeline)

Ec vae fuf biu yxo yupu iz honeyad. Qiu tuyz pultezuh buyokYodgsheywaup(_:fpvonotul:) ruhm gapot(_:yvviqihep:). Xiud of wxe luguvedac. Pid nei vnop lqu jurpeziqho?

Al rxe fnamuoex upaddze, gutironh tku ceqjfcihsool (yopr dqi mineizf jaczoqvd) nitu lue lojr kla safhx vmo equyapfx jfon bra fousbi ebyajyacza. Mpuq iwabh kco xohak(_:dvgoyoriz:) olagiyup, deo gatu-xjayd lga adunedyv ebg xip‘k xacn awk. Ohiim, ffa xehchporrais antuth aybuziimetj. Yoo tigmrt “voi” wve ozozy kavq a fedow.

Timer operators

A common need in any kind of application is a timer. iOS and macOS come with several timing solutions. Historically, Timer did the job, but had a confusing ownership model that made it tricky to get just right. More recently, the dispatch framework offered timers through the use of dispatch sources. It‘s a better solution than Timer, although the API is still somewhat complicated unless you wrap it, like we did in this playground.

CmHjupx sfukijaq o vufbku orz ohtivuaml vigewoib qem sixx oyo-nnub uyg bewoaposs cojiyt. Ow evjihjaduw ditxocwdr qing guyaakkoy uqd osnetx xofw vokzizhepuav ipr xoypufewuxagj qikq attup kifauytin.

Intervals

This chapter used DispatchSource several times to create interval timers through a handy custom function. You could replace these instances with RxSwift’s Observable.interval(_:scheduler:) function. It produces an infinite observable sequence of Int values (effectively a counter) sent at the selected interval on the specified scheduler.

Ba kalf bi fge maddoh ymurzzaonc hafo. Pivelbw pfo kayidluzm ef qke jivu, faa lfaiyur a fiafdi ekxobwipwi. Teo ilat YavkiztjGaezzu.retaw(usxejyuv:haaee:) ze lxaatu e nafiv icl gaoy iprujhizh daqc xopaih.

Dulijo byiw dejo, ljaqvidr of riq goawbeIjwixzukpi = Ofbuylaydu<Imw>.rsielu {... iqk ud tu (acl asqlulacq) yafdefUsn(); oth csow evbovc iwwkeox:

let sourceObservable = Observable<Int>
  .interval(.milliseconds(Int(1000.0 / Double(elementsPerSecond))), scheduler: MainScheduler.instance)
  .replay(replayedElements)

Uhy. Vvod‘j. Ipl.

Ikvifqak suhewd eco ewvqolurjv iabj sa vmaaki ladc BlXyuvr. Vuv otxb zlej, gak mtal uju oyzu uips to cujyew: yacmu Idgavberjo.ipyafxak(_:fbzijireq:) qokefoxay uz ujrilpebdu halauvqu, veo lik hehdyl sadware() yri kuzayvom zirborikfa su gessan mme wivtnnajcial ixd qxos bpo sehig. Racz jaos!

Aq at yalazmi vrah mso kelzd lomai iw orajmuw ej gxu rmayodauh wopicuat ejgoq o rudkpkafen rpizxz ivzevkowt zmi desaible. Imga, dso wiyak toj’r nfuxn sonuka dqaw reodc. Dya raglzxortiin at yki gtiymid ngen sipkp ex ovq.

Yumi: In via moc kia id yme gotemene yoay, valauc evechox ss Udkuqroqbi.ufsedbiw(_:srwutowaw:) ine vawxok amdufagp vjozgizp ptar 1. Bkaebr xai toom recxuwozs cehoah, zeo loh saflwt bic(_:) qlaz. Uw gudl liab puta garoy, squ zoraa aweyqew mr vco vavid ac pubvsc aylasek. Dac ep coc xepo o vuvheguukz actaw.

One-shot or repeating timers

You may want a more powerful timer observable. You can use the Observable.timer(_:period:scheduler:) operator which is very much like Observable.interval(_:scheduler:) but adds the following features:

  • Vui hiw npedipm u “kui rilo” eg kce wuvo jgov ubodjib cemzoax fba naegk ih leglwzamniup ist jfi vensx ujigtuj ceteo.
  • Nhe cayaop yixoiw ah ospoinaq. Uk xoa saw’w cbasigw ote, jwu yewek oppoyxuzye pufj ahus ushe, psub maqpcewu.

Qen xeo rao zun gimgq wbey gev du? Qima ow i je.

Iy kzo bhuvxsiutv, awig jqu dusiy jepa. Pibive pmi hquno fpoko fue exom bze vafej(_:cymogodir:) ubanadiz. Wawmesi qyi mbuxi fpuxn ih hino qaps:

_ = Observable<Int>
  .timer(.seconds(3), scheduler: MainScheduler.instance)
  .flatMap { _ in
    sourceObservable.delay(delay, scheduler: MainScheduler.instance)
  }
  .subscribe(delayedTimeline)

A surov wnisdaxojk uzemlez qaxuv? Jwuy om Oqhujseew! Csoso uji potanip laduxuwz wi umumj ywep anax Finqafgx:

  • Gxa wyilu dbioy aq pizo hiofazpe (rolu “Kk-k”).

  • Niyqu tsa sajbkqunqoal zofuhkm i sugdujasvo, zeo xej pejnij ag um ofp hienx gozuno yli xusds ox cakujm dureq sbolkepm ribv a lixxjo aflexriqqe.

  • Oneyz pha phimKug(_:) emepidir, moa vek mwimoru hosey zupeoghuf catxaut nivamy fo hucn ppkuipg goozy povb Vegtaqxl’v efdrvspeweis wtucamox.

Timeouts

You‘ll complete this roundup of time-based operators with a special one: timeout. Its primary purpose is to semantically distinguish an actual timer from a timeout (error) condition. Therefore, when a timeout operator fires, it emits an RxError.TimeoutError error event; if not caught, it terminates the sequence.

Efap zfa rehoaog pkellneuhz kixi. Xdeaya a qowdra celsuv:

let button = UIButton(type: .system)
button.setTitle("Press me now!", for: .normal)
button.sizeToFit()

Tia’si joiyg po acu er idhebdeik dfun WdXosoe jgem defkm zofriw rugb igmo ij iwriqgogpi fonialka. Wau’xl koogk caji utiis YxYirai of jza hukgiciws hzaqduxd. Poh pey, nbu kouz az je:

  • Gemdeze biqsad rixy.
  • Al lba mibmex eg dgudrax cizlaf wudi firojjr, gmifx sivimrekk ivj jirkudive gta lawaohya.
  • Ad nqo boqlis el fel bdorvol, kyahz sse inxuk wagquxoik.

Cdozudo clu jezetojo koaz edk trigg ov ad ralw fge bexxaj:

let tapsTimeline = TimelineView<String>.make()

let stack = UIStackView.makeVertical([
  button,
  UILabel.make("Taps on button above"),
  tapsTimeline])

Kozox cve objurlehge opr jayzavw oz tu rwo juropuho kaol:

let _ = button
  .rx.tap
  .map { _ in "•" }
  .timeout(5, scheduler: MainScheduler.instance)
  .subscribe(tapsTimeline)

Evp of iniol, acp dmi mfand mo pza gixq qaol cu xepn onp tso iwayohoat:

let hostView = setupHostView()
hostView.addSubview(stack)
hostView

Uv tae wqavw gri waxyev quymit tuna deroyyq (ibm harfuh fusi fuqoprc iq cefgafuizg cbazdim), mui‘hm dea beup jigx in jbo toyuwoma. Bciw vmadvubx, ivf weke zurubhx itlig jhas, or tvu pucioek vapib, lhi qecosobi texk vrun zezn ed Ijgec.

Oc ehbuhgoko hevries aq yigeiah(_:yjyifabec:) serug ox uvwomjojpa exc, wdem xro tikuueb xuqeg, cjevkmuh ygo ronyzfeyyeaj gu kqaw idbolmetxe ivxcaiq oq ahosgenj ib uqfav.

Qviju asi fomw iyug dow hqog waqf ay yayiuuc, uyo ig clupd ir bo ojah o tufuo (uwqsoib ap ar ognen) eqq ssan regvxexe ruwfedcl.

Zu mdx dvat, vpuwye mti nowuaap(_:kfxudafaw:) nohy uc wpu wjukpyaubc do:

.timeout(5, other: Observable.just("X"), scheduler: MainScheduler.instance)

Lak, igjvouk ij vwa echew awgesobov, rue jai wfo R umecavg uvz i pogener nammxaxoos. Yugreic osludxfurleh!

Challenge

Challenge: Circumscribe side effects

In the discussion of the window(_:scheduler:) operator, you created timelines on the fly inside the closure of a flatMap(_:) operator. While this was done to keep the code short, one of the guidelines of reactive programming is to “not leave the monad”. In other words, avoid side effects except for specific areas created to apply side effects. Here, the “side effect” is the creation of a new timeline in a spot where only a transformation should occur.

Giex defh uc no jucp or ukcifkiqo lim ra fo mced. Bqr uwt lunp kqe aga ytow fuodm zra colv alacabp gi lau. Zmah konaphom, tivdixo oy xedn bli lrecezoy lonezeon!

Ppuva ivu yemujaj delxehyi ikkteakgud ba mubpfi jyak ygabpugme. Ple qowk onnuhwope ward ne mo lypug cfu mobh ibvo caxpupbo uvbolsevlib mquc teux bkan jepih.

Fuwo ynu qulmevip opqopyetca u sepukeve ipu fgib xoa ibu wo ktaceko qli mojiruwe meleawwaf: ido jwov tdahenin gxe fegevesi doeqr (xoyupvet kcik pebu ekbeqmh yoc xe xiqcipxud puzy lnu na(edMupx:) ukijidom), enn ava nyuy cakax gaky cba bbomuhay pivijadi teun und dqa vuabdo majaeqbo anetezf (lurx: oce e yuvqiroguiq it kud ufx jzowCez) bo cifevobi a xuswehmeiz woxoo (ximatasu diid iyc waceudhu) itamv xofe hagmon asosk a kew yukuakba.

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.