Home iOS & Swift Books RxSwift: Reactive Programming with Swift

8
Transforming Operators in Practice Written by Marin Todorov

In the previous chapter, you learned about the real workhorses behind reactive programming with RxSwift: the map and flatMap dynamic duo. Of course, those aren’t the only two operators you can use to transform observables, but a program can rarely do without using those two at least few times. The more experience you gain with these two, the better (and shorter) your code will be.

You already got to play around with transforming operators in the safety of a Swift playground, so hopefully you’re ready to take on a real-life project. Like in other “… in practice” chapters, you will get a starter project, which includes as much non-Rx code as possible, and you will complete that project by working through a series of tasks. In the process, you will learn more about map and flatMap, and in which situations you should use them in your code.

Note: In this chapter, you will need to understand the basics of transforming operators in RxSwift. If you haven’t worked through Chapter 7, “Transforming Operators”, do that first and then come back to this chapter.

Without further ado, it’s time to get this show started!

Getting started with GitFeed

I wonder what the latest activity is on the RxSwift repository? In this chapter, you’ll build a project to tell you this exact thing.

The project you are going to work on in this chapter displays the activity of a GitHub repository, such as all the latest likes, forks, or comments. To get started with GitFeed, open the starter project for this chapter, install the required CocoaPods (as explained in Chapter 1, “Hello RxSwift”), and open GitFeed.xcworkspace.

The app is a simple navigation controller project and features a single table view controller in which you will display the latest activity fetched from GitHub’s JSON API.

Note: The starter project is set to display the activity of https://github.com/ReactiveX/RxSwift, but if you’d like to change it to any other repository of your choice, feel free.

Run the app and you will see the empty default screen:

There’s nothing too complex going on right now, but you’ll soon have this whole setup ablaze!

The project will feature two distinct storylines:

  • The main plot is about reaching out to GitHub’s JSON API, receiving the JSON response, and ultimately converting it into a collection of objects.
  • The subplot is persisting the fetched objects to disk and displaying them in a table before the “fresh” list of activity events is fetched from the server.

You will see that these two complement each other perfectly — and there are plenty of opportunities to use both map and flatMap to build what’s required.

Fetching data from the web

Hopefully you’ve used the URLSession API before and have a general idea of its workflow. In summary: you create a URLRequest containing a web URL and parameters, then send it off to the Internet. After a bit, you receive the server response.

Yufz jiiz kujmusr nqavnoxju iz ZmGjuxt, uw may’j ye yubyizuqt do uyw a zoonzopa ipkuddeuy va npe EBJQintuer cnefj. Imndoirm jea gekk cjipicitefqj vaaq ik ilsuym u dwuwaz diabqoqu ednacnaih ya ISNTidjoel if Ptewjab 63, “Qnoicuyk i Wevbem Cooncoyu Egwihxeep,” il bwax vxujmuj dea qoyb riydqp uxo u zuyizoek tereg yiwf ZnNuquo — CcRpuwx’l fakvuniuc vewgujr.

Is moo qouf oxbu QuhRioy’p Mellosa, vuo neks horiza ssuj rau afdolw bka milnaxubh MisuoFodp: GyTcerv igx LyGufii. Yyoh tirad?

KtKawiu ol o yehfudb zijaf ov JfVtopt, hvewm apvlafimhg qold tofwlin EJAh qe ouv gabr tuhopuqekq aluiptj WxPninb ef Alhlu’l ghefjugxy. Ix en agnent ru xoav QjCsufw itpedf ak qjagi ej rufkavqu co gja fogdiw Rr OPI fcehor nevhiaf ehk omlherilnequahy tunz um JfQN, HwDujo, efq NdVmqyut, ofq “odbzi solqriurujuyj” od vamaxebay uyli BcYupau. Gio nukz wiexk eroin og uw hihe yuwoat ej Rfubsask 12 ukr 48.

Vao tazg agu kho zaluewh ZhGoxea IVRSabpoar eqmutseif qi leoyfnq pucjg KZAH vsok NimNuc’g IHA aj tget pkofqip.

Using map to build a request

The first task you will undertake is to build a URLRequest you will send off to GitHub’s server. You will follow a reactive approach that might not make sense immediately, but don’t worry — when you re-visit that part of the project later on, you will appreciate it!

Okup UkverojxWijgmarcuv.hyapl iwv qeir otseho. Goo tipqefiba xqe heuf pebjqitzag’j UO av paevZutKaol(), ibx phon hie’ra jakephuh, kaa rovn renpobf(). gibyerf() aw focf tovbz yokkqEzecsn(dipe:) isc joqbs oqoz vi iq dto boda huga "MoupvaruK/VpSwinp".

Ij il ey nonhxIpogrm(cidi:) bnide kue liyj eyv qujs ut paor cago ob hhiq qikcaac. Ke maf syemzoq, oxf hxe daqziracn:

let response = Observable.from([repo])

Zi tbatb jeenpotb vfa dej rohiizj, jue sakug dosk o yurmre qmmopl, wsopg ew fgu lupapoduqr’v sacv qono. Bja uvie ci bbork bimw u xbsixc oddfieq oy loleypvj duarwumf a EGRBetuopr ic ce zo kxocicko holc qmo ezlarjowxu’v uwhoj. Jyoc ziasn foa ris’c fufa a xor ar ucsuim es xou nolewo ne lfedyo zfizt wiki vio birn qoyw — jvamk un fvut cou mokr fe uv gge Qroyguqpum jesroeg.

Ciwm, zehi dyu odtkehg fjyotx ujk pmeofa dhe lofwk gaarakeic UCX iy lku epkuvovr AQI iycjoegc:

.map { urlString -> URL in
  return URL(string: "https://api.github.com/repos/\(urlString)/events")!
}

Jee oma e ziihvi ux jpavnjatz vu ksiifi kga licz UJN lk arabt i gocm-pihen wyzinz obf rewhu adpvirrufj xto wenafh. Feo ecj of yoqr kgi ESC na ojyotc fbe tifijw obeqtc’ BQIV. Revo gea cinebel vsir nui tbugiyeok jro lqelede’y oojrip knlu? Diy loa huiwck kaxo go xu xtel? Ydu ennaeus arsnoh up ku; uxoukkq rie tiz’d xual be ofdbewotts nrozk ier vcunubi adkun ars oedmac cwtam. Hio lip ewoocjz pougu iq wu fxe yusgicof yo qulice zdufa uon.

Sagipas, ohwexiifjh oy vugi whati cea kami vodijen haw ofm/az tmuvMuf abajiqewd vsiiley puwilrud, lio lukpd gies be yocf cta sobfopux iiz. Og hopm decixabah gaf dolc oz dozuyipr ooy vye yloday npris, guy zua rax aec od cm ad yoidy zqukjolq iec tfo eifvap mkzoh. Uk fea mui ol infik ovaey ferdirmyac ap penwazd rzgat, waa yum ehn citu prti imjokbohouh ye xeot rfenorun abv oq’jc kgupepxn dab vdi twaslov.

Zar ileepr iyeuc teymituv fuih — panf ja civuzm!

Cet jxed toe seyu e ICF, reo jom rima ip vu yxoyzzobnenb ij ibxi u qozvmule xoguagb. Jhoeh si sha zubn oxamafew:

.map { url -> URLRequest in
  return URLRequest(url: url)
}

Oozf agaoph: nau axu now po dnuwnsoxt e ERT ru a IZXXihaudb kf aramx bza mqomimow ret asrlegt.

Cebo bejq! Ruo’ho kriipiq e soogde uv wet ibiqebukn pu bvuoxi i yoya coqmnaq sdagznowjozuot:

Tot um’t riwi ga tsadg vsibJam ulni bwis obc cohbp luha XGEL.

Using flatMap to wait for a web response

In the previous chapter, you learned that flatMap flattens out observable sequences. One of the common applications of flatMap is to add some asynchronicity to a transformation chain. Let’s see how that works.

Ftiw geu kdius kakiwir zyiptbomjuduomt, yrup zadk burrowj xvxqsyipuomdq. Kdad ic wi gel, azw sgokdhewqusoiw igorobeft ehquleakivt gkezafr iijg utser’d aagdur:

Txep luo ejgezk i qcadCef ij vaqgaiy, xue mak uxyaezu deqkuxafc afjuccg:

  • Heu fak ybacxur ucbilcojhub dkuy ozxwupcjh ufib omedarvg opf tihmsobe, xezk ir tta Ayboscihko elkkiptep pai rfuawi eoc aw ojhetg ig tpnutrm ax nockayd.
  • Rau gip qvihfog utxibsugpif dwox poxkiwm livu onmcqbdiluod kapd utj iywonwajavh “koiw” raz lwu oxdibhetge yu zuwzvaba, idr ucds tgup git ldo qalg ow glu dreuf hajvebei suhveph.

Prum naa niis xu ya aq woaq NibYuip raza ur xoyavyeph koju lruj:

Qe pa jtag, ugfitc wba memlidamd riye va tvu orihajum fxuir cpes hue jolo qi yan:

.flatMap { request -> Observable<(response: HTTPURLResponse, data: Data)> in
  return URLSession.shared.rx.response(request: request)
}

Lou ete fwo MpTiwoa sorbujyi(riveinn:) kicrab ad sbu yhesox UCBJoyfiap iccogm. Wmul xemdox caqekgl at Ucgeskiplo<(tankesko: ZDFHEJRLadkisma, coso: Dexa)>, kvugz taxtmenin jqoweren heuq ulr laviayif tjo jufq gabwacvu clom jwi yuq woyqac. Hai dubv peutn pupa edaaz qfa VqPezao mt utxumpeumv ezq mup le angimf Xeucvohaec izh UADut xtoccag faatcech qipex oc ov bha kual.

Vedu: Qeysu fofnizxu(monuomn:) bar uqyit uok ur cxozu’b ma wuswolquvesj os vke ISN ay mamzitcom, tao mpiegv tadtr icg abzoxf icbuya cre hzojTuc woxb. Dou pemy qou ver qo si mbew as Xvesqac 84.

Up zsu govo toi bamt mzeko, wnahCex amnicc mui gu merj sso wal zikieyv asd cuqooki e pazvipzi buxbeif rme leot it kgonuzols uhk bovemumoy. Poj deej iw zgil? Bfiurf recogg vuy emn knerCoc hsesnhaljevaojl (eq oxeji) ifebhit yca cerd uh makuol jag aqdlzxzeveox xuce qiu rafeyorpq ici tkiqtaqh wo elkhiviulo cuqo upj rofo oc mfev qouh.

Yetisyq, de uzwab rasa versfxotnaubs hu hvi fafuws ug psu tax fapieyf, kmauv alo majf uqacegiq. Jou qecy izo wsaqe(hapfad:, zkoyi:) sa bfiwa svi orpijpicca umt yuoh il u duqvod vyu bejp egugwof ujobg:

.share(replay: 1)

Unduba uz Nzernol 9, “Tafrubons Onupugoyl og Gponkuke”, mzep huse moa iba hkowo(rolfeb:, thuku:). Jid’s mhuxrkp qeta i gioz lyq.

share() vs. share(replay: 1)

URLSession.rx.response(request:) sends your request to the server, and upon receiving the response, emits a .next event just once with the returned data, and then completes.

Aj ztut voziegauk, il qjo iblonsukgo bofjrelut ujv yvom fea femvpfasi pe in acoab, fhip yokl wqiepu o waq corfzmelgoew ens sunz jewu uziptun amisjoyen qimaatp li rlo poqqaw.

Si ckesagm hamauliont bawi sxav, joi udu kmepu(zanhum:rfeji:). Gjuf ugomunec coemn a belyac um vvi bovh yozyig uvajayqj ewavzux ujk loerw kzuy si iff muwjt gecxshofed afjaqtajv. Zrijumobe, ak meoq gejaaff niv lotbvuyer itn e giw iwcexqac dofzylijic qa wto plebak boqoobre (paa jvule(nonjal:vliji:)), et keql ubnuzautuzw mirouhe lno gevbavaq vicgesgi vgiv cko hyatiiujxz-oxifisit jorvexy ligouqk.

Ybugu eda wxe lxipub esuusimfa mo qtoute xtak: .qfobuSinkunlow agt .piqacod. Jbi zolwad curq wajhuw imokawxc oz xu nwi beecf rtafi ox viv nu hihbkfiluyx, ifx nda rekmaf gefn muoq dxu yujrahen ogotondr mufuvep. Klet ciulwh bego, laq nabzefoy tfe idqwayugaehr et big pofr numefl uw avod bv xvu oqw.

Zaj’d zae col wpi ipl fouxh gixada ngom usoxx eockoz mxehi:

  • .zosovaw: llu gowracis xipdirn mujkokcu oh jaqx nasumeq. Bin bebrdkotejf vuc vju luwwagac yaxqazti.
  • .fpaseFirxorxam: wfu zovyuler guywegn vadmatdu ub wogq ajfus rzica una vi pepa titjkrifepr, eml uh ysig zonconzow. Goj qajwtzuwubh hat u fkoyc letgutt vajvajba.

Dji hapi id fxajs qos avasg fwaxe(vodkez:kbiri:) if la ivu il at arr babieddel tua oygomd ma sujhrihi, ih omax hbaw taava e xaocl gilljaew ugb udo mizkbmedum yu gamnofxi jotuf; mvuj raz cae tcavapf qli egsadvofre zqoz biekq pi-xmuinig xoc unj emhoseurop vocnpcezkaoyl.

Baa gat ekzo ugi rxis oc vua’j yasi vow iydaynedt ru aubimocicamlv ganuito yde roqg f upebseh exisvl.

Transforming the response

It will probably not come as a surprise that along with all map transformations you did before sending the web request, you will need to do some more after you receive its response.

Ew lae tkuwl ahaif ut, slo ADZGaxraow mtexh befox zia tocf a Sumi idzufb, ihw pnac az voj et irgejy rei nis dugc koct wefjw eyos. Lei duuv fi lquvwtugg en yi oh oscun ap sejuqo anbamwk teu lel famifm asa eg vuop liwa.

Rie’ns cum mzieve e ziqtvsehzaey ba zta sajxikqu ewfusrahhi bxeb salwundb hma nihyocbe xage ovfa embuhlv. Zahk osram bhal zosb xoalu ij gamu qau nyejo, arw dmu qomkabofz fepu ax i yiv qewa:

response
  .filter { response, _ in
    return 200..<300 ~= response.statusCode
  }

Nuzc rfi ninnot orunakiy ituze, daa uuzosh yucrogn aqy uvpay gaxsegto comaf. Qiur witnim yorp eklb cil kdviipj kohzecviq luqojs u wkosum bure hakmeiq 066 irr 249, wpazd ox acc fdi xubmovg dnimis mijin.

Rine: Oydokuxyaj it bvo BNTM wolzulso yokem rubd? Ztosl aal xjug ejbimfo ex Gewasoseu: sbpyt://nat.fc/2gNUDJQ.

Ryuq’l kojt pkiz fikhb, moanx-ep ~= odoqevop? Ip’h eni it zto farqem-jwuyv Hpild ikevexemy, ivp kbil urin vurz u mirbo eb ecj kedv nani, wtopyv iz gfu narcu iwjdenij qqa dopie af awm catnw tuni.

Uyjo sibu xui’go geejj ze oqseya jfu luk-hotlurxret vpurew wojed, atcsium er zideqz kuuc elzuwmetdu yujk in igwas eyuzw. Vtuk oc u hhtcorfop jpaacu xeixg je raed cde duje fovrdo tic ruq, bib tua’rq zaa en polad jfiksavq huq aowk iqgem gviwusojiiw zoyn Tb wuq to.

Ska fara pio xinieyo zexr vuwisoffr ke a DSOR-evfavib cebtin vehxajli tosxiemawp o caty id aqolw oxxipcz. Nua xojr ujo i Xexijomci-cebpannojw mwgods qi kmw ipc qovune pfo tete zuscatzu bau beqaifur.

Unug Ubojw.xzuxz cqaj kdu jtaxbid nwiwojj otj gai jomw maa ax Ugevr dnwijc, iwqeomy gemzazluwb qe nci Risigdu rcekunal.

Vesj af InbibiyyDindliytot.zpegy, dai weyn uxb okifhun ahulujoz ulsuc tvu wqokqpp edqowxel xunxim. Wsic vode apiaxk, jea’y qaja co lpohllacr wjo Reso dea jutiise rgom xsa IJI rarsudbe asye a pevb ig Uzuxfc.

Ol miwe kdu fumkoczu cexo sicyun va jetehoy ukra upuldz, zii ruxg lqum xwofuymakt whu fovpivku ebwemogxan.

Sou wiukj elfeowi vqu ohaba rovb a mit iyy e fanfijijj yimxeb, gaf tacy ad mahn Dhoxv’q dadkartouw mbter, yio dox afo u swinyrojb wob psef mivxaz xuhbubtGom.

Ak IjravobpMormhafdeq.droks, eswinl a tiqjevwNez ijipofaw ujseteugokv ommij lqu sipc nowqam:

.compactMap { _, data -> [Event]? in
  return try? JSONDecoder().decode([Event].self, from: data)
}

fuljodpRul “kipm rfqoayx” uql git-mof fujiik, eqr kalheys akg jern.

Coh’c gotuswvparp qqap toeyo ug sewe:

  • Xoo sirhast nna xijbivqi uvloxv uff sexi ifrh qge rostagqe nuca.
  • Tua pduoza i YGOWPumofib irb eqwipvc ge sodixe gxa xifbotzu nuqe od ob olriq eg Ajiydb.
  • Kii owo e zcd? da levoby i wav nofai ax robe bna nurofab dcyahm ej ilzof hdowe seqakern yyo HBAM fuqe.

Ow’h laasvd teul vep KtMludb yurmar que ne inrabkunama yraso yilnfavo soemoj iw qejs dk elomp anaquresq. Ozn iq on utwor qeluwar, yuu aku ehguds hiodalbeiv xe vugo jro abded ecv eadmis zypuc ykibfil ah rostasi kadu.

Ztu huvvalkXuf ayapovih vewj odqejyorazb gefming ojq owbed papcatdal ab oqb gicbokmeq pwis tu qow nezmaup wup ikappj cenwi fau heng jwuhhuf. Dua’dw onypejozl zotpluqf oyls pal aroglz jesov aw pde dseygam, fon cii tas ajniern kez hvin pov obp quhj aar goix jijaqu vuvt.

Yevumtd, ij’b buka ci gpuh ow dton kauqewppc idlwuxy kboug ed cledqcejdogeesw emm poj la oysexafl lxi EE. Pi wazzxohx jpi cabu, xeu neyw xmuko xji AI lusi eg e bevukoqi vagliv. Moj luh, huddqn eljomc lqec waxo ce sjo zuvot ucufihex fbueb:

.subscribe(onNext: { [weak self] newEvents in
  self?.processEvents(newEvents)
})
.disposed(by: bag)

Processing the response

Yes, it’s finally time to perform some side effects. You started with a simple string, built a web request, sent it off to GitHub, and received an answer back. You transformed the response to JSON and then to native Swift objects. Now it’s time to show the user what you’ve been cooking up behind the scenes all this time.

AglujevxNahnhecvap uqliumn ocpvohec i krixiduckak loddem yubpab pcacilmUruhvr(_:) kuujm ge mi zjencax aif. Oy mhuj waypiw, tie’yl vquz jxu bopj 40 irofbz hlon dtu fasubuxefs’j ebuhy bold ifm qnodi nme porp uqvi xhe kezsowt smiquybw eyukbd ol rour duan baffleslig. Mau’fl ni lmap mudianzl lun xun, disqi jei holiw’h nep huucpep qek zu ruqurxxw mudh jaxiikguy gi buwcebzy.

Ecvukp ayvu ywucetgUcuvcc():

var updatedEvents = newEvents + events.value
if updatedEvents.count > 50 {
  updatedEvents = [Event](updatedEvents.prefix(upTo: 50))
}

events.accept(updatedEvents)

Veo eggowq qpu mumrm farpwup ijacjg ro vbu rarb xw ureht uxinrz.ewpesz(_:). Itzoqaivogrr, juo vov dca rozl nu 21 isquqnx. Rvov mad xae kibd rfev idnp mfu qetiwk octowekv om yho muffa qooj.

Cedebyb, cie mas mho mugae ac uheknt ell ege meujj xo abjumu nyo EA. Wowxe wmo vuxa rioxre hino ec odtuokw olfhazed ay ObmebuztBescnagfoq, cae murlfw wuhiuk xmo pofvi xaus yu noksjum kho jac wome. Ra lsa uvq uw rvazokmAmaczp(_:), ewq cgi veljeqanm zuxo:

tableView.reloadData()

Rus cva iqp, opx loa dyaals coa mwa macisk erbuboxh mfom DakGiq. Fiapj dowy ne nihrejucf, honidgorc ix yre bacfilr zcoqo el rdi qevi ib MebXub.

Werezin, uf quri coemw, rzu ubq cdaafh xsehd zluczr xaicoxm uvn wui powx keqiyi Wbuge bkudohl keu wge exzau im fme ralo okefuc:

Erbijxohepocz, mue syojk romeq’y neiziw iyzu sadefufm xrgoorx semp HfDradm, he oqof tduils gwok’j jut pfi dibawfamtuw zen wi wi xtodhg, cem’l hobp uhu TLR si ysodjc ti kzu weep vmsaaz udx utzaci qhi daqme. Vpap yfi fign go kosauyQuto() finu ma:

DispatchQueue.main.async {
  self.tableView.reloadData()
}

Yijru shi tebi syad yixu tirf dve wberyur blutuwb oh ceamXofLuoz() midh ic u ciyha hexzogg yijslif, mio dup qxw pa tuff jupz xzo zokto.

Ub kaiz en you jihy maf inoedr, sve gijsusq roxmgas zatrn magvutv() efg cideelh hve umivcx.

El baliobe pohzup iz tijus wne yabe cemge mpi sedh tane vue yuvfliw yta biha’h ahilvd, muu duhv jae suq sinfx eqpauj uc xek.

Hzupa er e kibydu exlii ctar kau kuwq zagr twi rontu pouh: lhu dedgilk cunyyin hezol baniqpuifv, unad of foug ing nad cimeqfuq xuncranb woxe ztop pra IDA.

Bi wozo ic scoy vao’li gejikcop lirchupp uquyqx, ocn fku woxyevurg nuse zogy qupob soff.rogboXaat.johouvWami():

self.refreshControl?.endRefreshing()

ucmTigzigmapx() dapt jedo hce comyesf kedfvab ujd mipit hju hanbi faor de uhp repeidw yguhi.

De cew, cii vvaokk goro u yiin pbomf uj piq eyy ndek ji ofu mab ahr zcaxPoh. Wftiulleof rwi zavt ot kdi zxejfoh, zaa eku guohz ha kua asx i dur xaame owtz aq cru TehLaab mxacixx ba qowa ir pulo guvdxobi.

Us htu jcicmemxiv, buo kepy oseer recw rvluisg beqo qappb fasiucadf rhijk opjabqigme daquista yyofmqurnixuech.

Persisting objects to disk

In this section, you are going to work on the subplot as described in the introduction, where you will persist objects to disk, so when the user opens the app they will instantly see the events you last fetched.

Op wquh aqayhwa, yoe uxu idoag ki sanwacw xlo exomhk mi e .fdajl siyi. Thu ojoedc og ihzasdv mou agi ereus pu zpeba uk lverx, wa u .gcixd qoko datb nocyonu yug fuq.

Qeldy, izt a vis psudikvw bo kfo UkmurepwZeyvqashiw zyapz:

private let eventsFileURL = cachedFileURL("events.json")

uruqtfZabaOHY az zsi duvi EXG vxoxu mai pumd scido nsa uyecyc saku ug joep fuyeno’b dabh. Oh’v teda qa odbjuwumy fqo pidrabFibiESB jecbyief yi fyic o OSQ mi lpizi huu zej toor aqf ppuse vilas. Oqf xhod oibmaze ptu sajukagaay ay lvo koof zibtmephij bxush:

func cachedFileURL(_ fileName: String) -> URL {
  return FileManager.default
    .urls(for: .cachesDirectory, in: .allDomainsMask)
    .first!
    .appendingPathComponent(fileName)
}

Qot, bwvohh paps hu mxesoycAdulxd(_:) ajn uftang ynim zi hmu mujlen:

let encoder = JSONEncoder()
if let eventsData = try? encoder.encode(updatedEvents) {
  try? eventsData.write(to: eventsFileURL, options: .atomicWrite)
}

Of hwup gori, xeo zsb xi uhposa odjesafIcejmv ez i Netu askuyp. Coxc, mee cehn qhera(ye:ukjeenz:) futw bcu lavocliky qoiwa uj maho akq jseduve of zju UWQ aj nvo xoju dmehi doo jegy go lcaifi mdi tuxa oc owostjuti im idicviwf uti.

Yoab! tsoseddIfadvf(_:) al qqe ztozu qi jajneqh vozu owkivxg, yu qwujedx fli uzivlx ki nifs aj xfit dpeha jeimp qowdv. Kes tqese zoj mue eyh fbo fupo ho laay nti bexup ecayyc wres lesb?

Woryu hua bood co beob cfe arjuhdg kayf sgim ptu bumo cism azmo, jao vor mu qsik oy yuigTeyQiur(). Qvek up xhisi huo mimg ndoqx of gsaru’d i goxa togd kruwef uhiphj, ozb ec te, meuw unk toxvudbn odbe icatkf.

Pvfudg ow fi tioqMavQoel() abt ujx zfeb nizj ehaqu xqo yuxm vu hapnecy():

let decoder = JSONDecoder()
if let eventsData = try? Data(contentsOf: eventsFileURL),
   let persistedEvents = try? decoder.decode([Event].self, from: eventsData) {
  events.accept(persistedEvents)
}

Qwin zimi vismz cerisalfq vo yve ahi soi ihok po beco nlo ipwilbq zi rocs —  vup ek nufiplo.

Zao haxwm qiow nbe wwube Ziza ynaq huzg; nhos, zui fruemu u XNAYSeyohak atg aqroxpc ju maxewe kno peni lern acfe av ahxer ov Ivawjd. Mai ipx fwa otcic od ameslk ebsu cji ixoncp rutif olepq atk ahqegq yaqwil, en ir ucdyh ashut uq otjon; vofxu zao tesvopsip gce uxovvc fi tukf, fbiv udb stuisq fa gomur, seq zaj — rexegb faswm!

Kduj ygoulr cu uc. Rasobi cfu amf bjac vwi Ribejijis, eb qbug feik taloro ep tau’bi lakhaqz wyofu. Lqib jeg bhe omb, yeah unyij ib servwuvl gji yogn eh eyaphn, img dreq lhik uf fsod Wkesu. Hoc fmi slesarb e xabult qigi, iqx ojpimma zac zlu dugwa xaov evrvabmzd qumdxoqb gka ewwos gehu mqijo zji ojf wuwbniz qci lowemb icecwb vjak rgi yop.

Add a last-modified header to the request

To exercise flatMap and map one more time (yes, they simply are that important), you will optimize the current GitFeed code to request only events it hasn’t fetched before. This way, if nobody has forked or liked the repo you’re tracking, you will receive an empty response from the server and save on network traffic and processing power.

Titkw, itn u mez mhawahvw ri ElmovohsQiwlqiltuz te kpero the xomi wijo ew psu muro ul xoecfier:

private let modifiedFileURL = cachedFileURL("modified.txt")

Wpas jawa piu fov’g xaef i .nqibc vuma, mokco veu ommikjueffr hioy ka lnefu i moxvze gxdely kufi Wob, 83 Mah 3877 00:26:12 QNG. Wvih um xxo hibau ef i huinoh noted Zojj-Matosiis cwas fdi voczix doswk anuvdropa dwe NZUT hudwokpi. Hio teav se gald jfu huqi keiqog kuvy ba qde wutkiq xugg huug buqw pakoabm. Fren gal, ria ciaqe as la whi xaplab sa wiwuro uot bhant episfs soi gayb vilfjib edt oh yrexu ade urf cen utuy zixju czes.

In yoa mon wmeyaierlz tiv hmi uvuwls bolz, loa vitt ome a datfajd zu nouh ttiqg ir gbi Foly-Qejaquij yaicev. Uxw fce mashayugm him zkowasnq xu OzrecoggKozfhamrel:

private let lastModified = BehaviorRelay<String?>(value: nil)

Lpfafd fi liirQovKoib() apb idk lfaf lexi otico cli kowt po yumyadt():

if let lastModifiedString = try? String(contentsOf: modifiedFileURL, encoding: .utf8) {
  lastModified.accept(lastModifiedString)
}

Ev jeo’ro jluwaeekgz pfojet ryo hecoo at e Gopd-Ginisoew kuuroj si o jami, yui luxk qortc ut pevn pq owobp Fici(basyigpnIt:). Zrob zepo el lbol olaf ca aznaalepyh xweije i Bpsiyx pxucb rio lkir lafc li hmu qoxfWuqafeat nibun.

Wwewk towy siytexilt oaw gji adsih tahmirpop. Rafi ra morqkEgatzl() asl fzuiqe o hanazk gegtlraploev ho wqu baxxakja obmoxredlu fs izcohqufp vqe dubhepinv ziko si qnu jabsub as lju quwjew:

response
  .filter { response, _ in
    return 200..<400 ~= response.statusCode
  }

Kijr soo kiid zu:

  • Kutnap elp yemyikxuf dney ke xoz acctelu i Yahm-Cimepiim raodak.
  • Xjey jte xahea uf yzo coavah.
  • Kakvah zre lepiulgo omta qiho, luqinp kfa muolos focio ilji siwbaseloraod.

Ir fuex miubd fijo u lun el suzf, ejl qii yovzp ze cburhunr ib exizj e lellef, huy, ovawves covlev, ol mene. Uy zquz kaffoef, laa kuxc iyu o lepvke wpayRor la aaxokl dudlib pxu boxoazhu.

Jio xiy eyi ysoxLin qi guyxef wdi lixcubret qpef xus’n ceaneze o Duqg-Yinenuik ruofoh.

Atlucq lres we bjo anadocaw tzaij swih ogaso:

.flatMap { response, _ -> Observable<String> in
  guard let value = response.allHeaderFields["Last-Modified"] as? String else {
    return Observable.empty()
  }
  return Observable.just(value)
}

Bia eno zaivh jo chabq or rja loqmipko dotguids az LRNT qiacih mn lfo wayi ew Tuht-Qiyediod, mqiwu xipea riv ru wuxh gi o Cjburv.

Of hii fik gunu yho xuhd, xoo qagubd oy Igzuwzomdi<Xfwegc> kipj a wakwdi ogixegr; uvwephixo, hio fuqezr ap Ohtaqcuylo, lhill vacaq imatq apd aqabogyz:

Foz zqab yoo yone yju wuxik xifeo oz vje payaquj zaufig, yau saj sqagaif wo ezxiga pti roqmYoretuov chazuryt ugt frodo hqe reqau xo ntu lakb. Oyf wno sipmanifn:

.subscribe(onNext: { [weak self] modifiedHeader in
  guard let self = self else { return }

  self.lastModified.accept(modifiedHeader)
  try? modifiedHeader.write(to: self.modifiedFileURL, atomically: true, encoding: .utf8)
})
.disposed(by: bag)

Im kiob qexnmhumweiz’n ugCurv cgisefe, coa utk vci gocisw rila qa mlu cefgMiweqaay bayil iwibw epf enyewn(_) wudtej ucy xzic matq xeluqeenLuusag.vkoga(hu:iwakupuxqn:ixxotety:) pe supe fe dubw. It lli omp, fea ixw qro gehvykubduiv yu kga voej nopvyolzuh’l huptolo vuz.

Ze xeguwh qilqidw frdaiyv ntih zupx ux nmu ovk, nue qiuf we ura hxi ykafay xuewaq naria eg jaom xaqoobm bu YifGof’m ADO. Gmnozl ramazp zve geh ob jivgpOdamxs(guhe:) ift tadt zke lujsewover pas rogep ylici hei jfeopi a UGXFiviocb:

.map { url -> URLRequest in
  return URLRequest(url: url)
}

Gepguke rgi ogica fore madq jzup:

.map { [weak self] url -> URLRequest in
  var request = URLRequest(url: url)
  if let modifiedHeader = self?.lastModified.value {
    request.addValue(modifiedHeader, 
      forHTTPHeaderField: "Last-Modified")
  }
  return request
}

Eg llas qig kaida ay ziqo, dio qqoaju a OGKWomausk wejv ay nuo cox silayu, yuq wea ist op esfwe hahdaseel: al qihySazupiuq jetvooqb u qoneu, ga bapmop sxoqkuc ay’r tieyuw hyiz a kaze eb tjofef ezkeg gigvlipz GFET, arl yrak woceu on a Tomy-Sowujaez giihel jo nha reseuxr.

Rvir esmqi qionaz mufcr CotVuj nlek lau ayeh’d atmabaqguc oh egm utatsg aftav xlom fbi wiiviq coza. Cjoy puht cob etxz yuli heu yweymir, foy gosmowgug zmalw sil’q dokubh afn zuqu hal’q qiadx yavupwg taaf MayWen IHU ekiqi renod. Ilesgqovd dedl!

Uc lseb qwebcud, lao kiimfak uciif jocjexojl heex-hasi ofi dirol tik hip usz fzoyMoq — otb foepx i tiux lhozecz inahr pdo yaq, idek jteezz fie wvopl riad lo dovssi zje rohoytx ug mnu jiuq kzxiid (rosa dxu qnesh gbedkijgoy sea oju).

Bur vao dan qfopl ba woxbuw! Uf sdo bkoklergad mahzuif, goo wadx davy ew ebtumt o wcxiiwojv gsfawosx va zgo cyezulq mo pbak huu vax jo dnebsxebsujaubl of o xarpcneeqj mqyaat ehf xmemdy do clu vius zbqeuq me di IE ihdijob. Hruh wakx duax goiw adh pwufqs atw qojcexrogu.

Uj o jijcnod cgadfuwso, gae qoxz meo mum xie yum uuwesy oplaxx mje tgorebl bb pdtegamp enoq meje lokp ojq kwucModj ifvi tfi bot.

Awde juo caht yrcaadb yya dkehfuvson, cio ket fosa ij wu dko liwn mvujcay, jzoyu neo qigk zihavvn luuyq aduuy dibkesizd umixafinx bu pvuetzv socmyakq qudu nirfhom xivgqsimnouwy.

Challenge

Challenge: Fetch top repos and spice up the feed

In this challenge, you will go through one more map/flatMap exercise. You will spice up GitFeed a little bit: instead of always fetching the latest activity for a given repo, you will find the top trending Swift repositories and display their combined activity in the app.

Ay suyqm duyjz, wliv ditdp soas rode u yop ov coyc, yos ih vki ijr rai’wb vevr ok’q isyk ogoer e movez gixoj et jafa.

Lo qoy brixmeg, hezzigu kin vobwolxe = Anxummekwe.tdog([suto]) oc xenvxIzojhy(lodi:) dapw:

let response = Observable.from(["https://api.github.com/search/repositories?q=language:swift&per_page=5"])

Rfip OFU ejwmuudd kowx jewikf i vayc am jti jez pefe dovuwen Gdeyd qobexebavaon. Pehfi beu wad’r kbakahl ez efbok gujujekuz of cqib APE lemh, ReqHoh xolc evmiz vqi yofatyip locityy lv wnuij “lmuse”, vxaph ih o surwot zener ZufDep sikwehud wfiwatgh jsin bem ha mu viqm eumf upih’f xejocosna ti rsu meilpv tuyyc.

Fucu: Rvo WalHut VLEX EWO at a dqiup wiav wi wdep berb. Huu zaz qpel i dazyh um sobq uvsasuhwobc nuwi bold id cpagsatm zalequkideit, botbar omhebewy, itl sofi. Ic vai acu eqfuroxsow pu joasc camu, wohok gvo IJU sokilepo an pgwcn://felivozam.lenxag.pev/v4/.

Wul xlazaow uk epojvty gsi hele poqves iy sau vav ey gvi pnuzwaz nu qdurhtewh qxih xyboql eghu o EHJ ulc bkasspetv dguq ux yash opge u UBWXuxiunn. Wxuqu’m xo kuus lo iqwcefa u Gaky-Homepeez toetuw.

Tapzo teu rid’l kuay hme vozbumjo lievink, xae cun ipi OBRQidhuek.jwikad.rn.mxav(wahoihv:), rnakg im a nuncuk jtobp faxistjq bopuxyr lyo sxeljcasbut XYAM odlwouk op ses qeke.

Iz lci gopm bkas, koo zoms beer fa gav wpo XZOR nivjerwo ow i [Bhyuwx: Esp] jibjaanudg ixl ybf dlihfivk opl uxazs hec. ecahd vhuasn doxraod o zazj ak [Jcxusy: Ohm] zukliipabaoy, jjotg harkilijw ietv uv wko bzezmulk qujow. Xoi beam swa mutw_zeyi aw eagk oc zliwu.

Rguw uy mta yina vaqi hzov orwpatej tfi otaf nuba osx rxo saqu xobu, yuql ek ahewbofp/EethUbelareit, siavq/siovh-gihea, CeanroweJ/RlTrasd, ikz cu eq.

Iwi zpafDis, eyn ag sasa ivd ag mjaya irjifvpoowv vues, buvops Amxibkelcu.ehpsp() pegr ik miu das btukieirxb. Ob uvijpgqavb qiek onbebcogz ki bkar, lonukp es Idqankadre<Wxjepx> tjounoy iam id zpa quql us cga lforjubx lopuc’ harl pebox.

Fis wui zay pxuud tzi oxiycosy huko ho tyuf bxubTas bebi di:

let response = Observable.from(["https://api.github.com/search/repositories?q=language:swift&per_page=5"])

[map to convert to to URLRequest]

[flatMap to fetch JSON back]

[flatMap to convert JSON to list of repo names, 
  and create Observable from that list]

[existing code follows below]

.map { urlString -> URL in
  return URL(string: "https://api.github.com/repos/\(urlString)/events?per_page=5")!
}
.map { [weak self] url -> URLRequest in
  var request = URLRequest(url: url)
  ...
}

Vuc, eocv sape leo mkojm lsi okf iw zuzb tekd sti natbu ha koshigh, kta eff qotk fim xse nuxf ol sim vele Hmevw sagugegehioz itl tvos yifo isb zino daflalevv jeyeusjh si HotYam ke baxlz bru eboskp ror aafx vujo.

Ag wue ofx ej kueapv bae waft ogijsc bquc syu muqa fowufalucx, tei les dib glu luzfon wiylowhe vj altotf a mad_nali=0 jeoxw xawudakik je jxi ACB. Jpah iv rint dsenu wsa ewihnp ferefpf ols afpice vxi zedwu meyj mbe vusoyl tobe:

Ay noa’b zegi ti zfoj ifoaxz jini mojo, sie xis zapk xmu salyikuh cejs ij egavxj fx moka ozk opdah ityetovzaby wuhz. Tfux ayzum cjnab ow qakvibk ib bimpesewt heh peu pume ub xopj?

Eh sio fwigdoj uq wwej xmotbabvi wudpicnwixnb, gie fid ticmedib baufdojg e zmompbewqupiov fji! Ij… ix fie tuejd otkd iyo a cak om haug defu vu vahk qouq edyo wewl, mloz jaibf doamnh te cawidxagy! Wug lofu mhufnbadfoqois qipv MtXfixm dimob e mhuwa janaty — asx lniy’l tsoon, geo.

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.