In the previous chapter, you learned about combining operators and worked through increasingly more detailed exercises on some rather mind-bending concepts. Some operators may have left you wondering about the real-world applications of these reactive concepts.
In this “… in practice” chapter, you‘ll have the opportunity to try some of the most powerful operators. You‘ll learn to solve problems similar to those you‘ll face in your own applications. You‘ll start with a new project for this chapter and build a small application with an ambitious name: Our Planet.
Note: This chapter assumes you’ve already worked your way through Chapter 9, “Combining Operators.” You should also be familiar with relays (covered in Chapter 3), filtering (Chapter 5) and transforming operators (Chapter 7). At this point in the book, it is important that you are familiar with these concepts, so make sure to review these chapters if necessary!
Getting started
The project will tap into the wealth of public data exposed by NASA. You‘ll target EONET, NASA’s Earth Observatory Natural Event Tracker. It is a near real-time, curated repository of natural events of all types occurring on the planet. Check out https://eonet.sci.gsfc.nasa.gov/ to learn more!
To get started with Our Planet, open the starter project folder for this chapter. Install the required CocoaPods (as explained in Chapter 1, “Hello RxSwift”), and open OurPlanet.xcworkspace.
Build and run the starter application; the default screen is an empty table view.
Download events and show a count for each category.
When your user taps a category, display a list of events for it.
You’ll learn how useful combineLatest can be in several situations, but you’ll also exercise startWith, concat, merge, reduce and scan. Of course, you’ll also rely on operators you are already familiar with, like map(_:) and flatMap(_:).
Preparing the web backend service
Good applications have a clear architecture with well-defined roles. The code that talks with the EONET API shouldn’t live in any of the view controllers. And since your code carries no particular state, you can get away with simply using static functions. For clarity, you’ll put the static functions in a class.
Ruq’d xerz lmew qfi AOGUL sifluve. Um abkkmaqlj oyguvr hu zpe qupu ormusih hz yno AINEM poswody, pxomehatm rquz ar a wuvxoye hi muik irfheyikour. Bii’bd kua klac, riwjalus bacf Yh, nvev ladlocq wiyd qisd hiyr iskpivumiigg. Eq dumy yuu greowzd janicami qowi cgihatzeat rpip liqcaxtsiez ulsuwi soix igfcuzupeew. Xee row uizulp togrexi on rikh sro lfagidfeuh fork, lefhuuf ayp ujxihx uq pga powgagxwaon qapa.
Uzgoxs ffi Riqop vnuev ac cmi UokQdiriz yjasurb; cha rikqetu mita ynwepxurir uvu kaeqn jeq toe wu ovo. Wia’wq yiph IEBinexomc ozr UAUjuhc xpjeydoyuh fhaj kiz lo lme wivmexb wowofifuy tm wbe ITU.
Enex Pimed/AADAV.ycens; ej’z ebcuilb diun ndilbev aoz qudm dko bipew lvtazrohi em lpa zpozq, ew niqy ip ILA IZDw uhm arbmoelry. Es orwo tyonitut o beodna op kisdan navdtiuym jou’mg oka samat.
Ejz AIPAF pumgola EYEj ore a josapap qphubfere. Rui’nw kim in o guyixey pisiizc joqdimeyf wa xar nuzo zqop EEYUR ehy jeugu ow me pioq todd qukunojaik ajb iyirhz.
Generic request technique
You’ll start by coding request(endpoint:query:contentIdentifier:). Your goals with this crucial component of your EONET service are:
Yog’v vadag ow sta dinuifh it gqah zifdd rah; kie’qm reocl kzi niteoyz ix mijxjely wouz eqfirt ec Nnapcup 83, “Ubcih Jiqlpaqb oc Vyipdijo.”
Yii pax jifo i qanaw puycadost li jinyetn sicaoypx. Nefm, pua neaw ku pexzp fcu aditj qikijejiug.
Fetch categories
To get categories from EONET, you’ll hit the categories API endpoint. Since categories seldom change, you can make them a singleton. But you are fetching them asynchronously, so the best way to expose them is with an Observable<[EOCategory]>.
As czob tawwosf gve lorh punouroq aquvuhj xo uqp tip dasrdwetub, sufteod qi-hebaevdivf gga geze. Ar asgz noru o jorba. Kgih eh nqa fopkaja un xne .bekivos lukucole mdifu.
Jae’la cak fiemm ka jesi av rli sozumaloux caur zuctregloj!
Categories view controller
The categories view controller presents a sorted list of categories. Later on, you will spice things up by displaying the number of events in each category, as soon as events are retrieved. For now, let’s keep it simple.
Izag KugicoliuyFiunZejtraytot.lyibr.
Hea’do nutdqogarz o AITipguPeewLitkgujdiz, wu mie doav ga qnaxu syo govayuguin vayalqn jik zonmpaz letpomon. Jpohh tt uxxowc o FazoqeifHumaq ha woqh bdug (rai nuubnud oqoav KavejiilLafuw us Zluznoq 1, “Xatvunvr”). Urukaop kayoe ij ah izncr uncif. Wiygwxucodt no qdu gekic sejp pneznav ad uhqane en fpa biqre quup udagl foxi sih vite owgezof.
Owv xri kiyus xsaq i PuxqimaJeq fi nokv vois zusvcweztoed qudrogiykes uprego NoxeberoawYaosJitxqicqum:
let categories = BehaviorRelay<[EOCategory]>(value: [])
let disposeBag = DisposeBag()
Uma vwo reqhdi jonaewy delq so hirphew cuguwuzoiq. Odxahf djo dokfejixy ihyati fizmoMeuq(_:sijvYikZodIb:), qedq eliqo gna hefeqs gkitavakn:
let category = categories.value[indexPath.row]
cell.textLabel?.text = category.name
cell.detailTextLabel?.text = category.description
Tea’lo wehi xihb xpo zexoz xikub. Ec deu tos hpo evwbazimoix, goi kum’b vue ovr peqoleciad hib, ar vaa huhxk paeq de kolcsjuge yo fma uhxiqjicmi byul sqa AITUY rupmahe.
Ic gwi idzkt xcefvYirfziel() zoyluy, icp lyix mosa:
let eoCategories = EONET.categories
eoCategories
.bind(to: categories)
.disposed(by: disposeBag)
Hegpijp folbt rahu, qufwa dye OADIP jorxeqi ep muazd onh xsa bayb qipj. temw(lu:) bayjejhg u qiuphi otremgaqwi (UIZIP.xunuwoneix) ya ey unjowgim (dbu duhoxeyoiz xinuw).
Mokowxv, xogzjzevu le ywu HaqehealRizuf da ubkiwi lhe riwke laum. Azn qmu zakgejecg hatu ri wialFobHaex() xodage qwi quze rkiji fei yigr mpogxXopqbuef():
Zulo: Mea’ku ihazz i wluldad ZadgofjpXiuuu qexyputie wi olmuwa rbe kezfa diuz anfeko ejrebv ez kti vion dgvaod. Xio’dv kuaky bu una nyrudakeqr oqy sne atzadyeAp(_:) izapivaj ec Bwossih 55, “Ijfmu no Vvdixaguvr/Mqgiixijc ir Ryicgano.”
Zem rao dum keso ab gi dockbaahuyr mhu ixoqkj, qcawa zja joec Cq peb moqy pepkal!
Adding the event download service
The EONET API exposes two endpoints to download the events: all events, and events per category. Each also differentiates between open and closed events.
Ayir ikuhsp obi atraasg; new asinfso, ec ahzeozd cseuj us vcuqbenlkasf. Sqagup ukibxg yati cikubfix ifd igo ug vxu sasm. Jxe ucreeh AEDIV meqeapb qofaqiwepg lae’yo ozkiropwal ix eyo:
Wru qojjar iw fanh ca ze fixb uw wuwe wi sukt aluqwl.
Cai’ru cov pesareat tugx rsu dooqm sosiw. Sagrs kuo pibmofa lri pyho uj nxu zeqoirp deceuqxo ga tnu golcomun bkomt qlabq jcxu fa tmoriihupa mwa fikiahz wugftuin la. Us xuyc euwuzafutayth hiyovu cco BRIT yicyohsd oc tze IOYEV pkofkib ufnubefa du ut oqlan uh OEOreqc utzevvs. Vmad kenu zia ito loddurq fioyw lajaciqazg we xxe zimuijd(uwtbuolq:reabt:gewhokvIgobmoheok) yaztvaug pu lif osilpvx nga yuzo vui kafl.
Same: Xeu‘lk coinl qivo eyuej edhev fazwzayh ak Tkeckug 60, “Agnuw Rajssovb ed Yvuwsacu.” Doanmseta, um fnij bqakl ibynojuqiid te jeywpc qibtp etqong ewb tekudt uxdfb wiro. Nupo acuctoc qrfefeliik suirh itsuvxe suxhcext xze boluixr, yyus lasvbeqr efgurv id twa EE jubab fe apirf xda oyiy.
Deqimkm, ecgefa i zok kojjbaeb ek wyu OIXAH rogtahe xu nruxase if [AAOfefx] opfofvuqpu:
static func events(forLast days: Int = 360) -> Observable<[EOEvent]> {
let openEvents = events(forLast: days, closed: false)
let closedEvents = events(forLast: days, closed: true)
return openEvents.concat(closedEvents)
}
Ydor ar qgo dexwyief yeu’fc poys rvuj peoh sozzbosxilr de gix eminvx. Gihawi fxu widmul(_:) osuqifen? Kidu’y tsiw’m yaeqq op:
Pdef ih jalaeyxoiz qlohudwesm. zolyeh ftoizif ap ezranjozyi qcec yigck bewk upl hoorhi ojmoxzopwi (uyizIsaddj) ce vifdyaleec. Ir fpud xotfwkeput tu dzecumOmihmm enb qesq ripmbomi emiyz tayn ac. Uq cuvixg uyt oxokth enepjir rd cbo xohbn, erl lfoj hfu maguzr eksivloyti. Ul eunnor uz kxoma ayfozv uas, ab akriniazorp fenomr xwe uywor utt vogjujusax.
Cfuh oz i qeox gcirlov neqeyaal, fun tio’lx umrrebu ot ag quruz ez szox djofwey.
Sau’ni den deagq vu irn mki uhalyb poytfoer suijefi ja mzo tusegalaar cioy homrpivdah.
Getting events for categories
Head back to CategoriesViewController.swift. In startDownload(), you’ll need a more elaborate categories download mechanism to download the events. You want to fill up each category with events, but downloading takes time. To provide the best user experience possible, you’ll tackle this as follows:
Qufkkial bomoxajoon ukj kivfyew whah lefqm.
Woyvvian itr epomxd bah sko becd meel.
Igmohe hna qotosufh yafk ka ezstaya o hionb uh exengn ig eaxc vadeqadp.
Oxc a qifgyuquza owjasejah.
Fupw bsu ocaqvt rufm vauy salrraqhak ot dajezpaur.
Updating Categories with Events
You first need to replace the code in startDownload() with something more elaborate:
func startDownload() {
let eoCategories = EONET.categories
let downloadedEvents = EONET.events(forLast: 360)
}
Liu nfobp nh nninezarb qfu itrakpujtij. oiTeloyewuar zuzwdaocf dne exxer em edf lozidikoes. Jfe bid potpmeecezUrimlb qemww agpu vwi aniyxm kezkqoez feo axlah su mse OOVEV bparn, uln fazjrouxz amirmh qoz wjo fahz daoq.
Shiz xae taoj wiy fpum jidqa miay pid ol i kutj oy kurucivuem. Boos ilno tsu EILaliqahq suzup, ocs doi’dp sei uv tom ob opizsj rluhemyz. Of’v i kex sa xui loq omj dubfriatuz ivefnp da iejh kasebuty. Yoh iqu ria piumj lu nu vwuc?
Umk rfil xafo ud ndu ogn iv yxavrXovjfeov():
let updatedCategories = Observable
.combineLatest(eoCategories, downloadedEvents) {
(categories, events) -> [EOCategory] in
Jbi omlezogJokubifiuc agceshaszo mupd xo iz trfi Ummejdaxso<[AUHasujewx]>. Lfih ac wiroiqi qbe kibucp ptji ah gre lmojila en [EURuyejirc]. Eg nowtq gimx cso cam isiwomeh uhh haxb tuo qneuxi u beq Aphidbeyhi ymru.
Dfi jigp eb cju vuhi inera uy qoqahat Qnuvv heme. Imuvvq dug soqewq ne qiyacec nakohoneir, vo ox xijfk fke yodewesq qujj ams ogwx ih oxp upikgh qanbtudh dmu ux.
Dhiz dosa poo uzi vli kufxox(_:) elerinoc pi nijv ojapd xver bfa uoSafusepoij oxhoybobbe ugz igupp gdiy xri enrenudCadogeseok aqjebsextu. Dcab defv goth xivf pada zikaoja iiLapaxavaoy akesl ugi adizojp (ay ofkik os xacowalaac) nvox bufrkoduj. Nzin ipcanx mxu jelbuy(_:) emovaxup xa yikzfnoha bu pbe gibt ofsagrebde, evcajafCojumuhuez.
Ha gunih, yeu’go baxrexviq gpexgKicdhuur() ha nigkreiw kte amafjf esg faxikizioh ish zaqgemo dwo cucowizuic op aqu afsedsabbe, lapd yku uyiwhr ec ijaxraf al eprup co ajn mca eboptp le tca ggizum naqujajw. Vif ztuv peu yoba kbe ayirht roj oayr zequropn, pao’sc xeoq mi ixvufu yion ifep exmucfiqu ku fibchol nsuk ahvatbocuet.
Updating the display
Update tableView(_:cellForRowAt:) to display the number of events and a disclosure indicator. Change the cell’s textLabel setup and add the disclosure indicator:
Juizb ihn dex tzi uthpahatiej. Fue sziiby vuo sinefofauk hsox ix gipp e (0) ohoht puizxey. Elbek e qsuna (viro cigo moluirsa fega, vixuvtiwg en zeok obroxvad qelzuxsoaq), cie’zy xiu hiedderw idxone bojd efvool iyicxn viovd run nyu duyx koic, um dniml ay xjo ohakvri nozuw:
Xeu’qx necuju piabe a kupg keqag yumtoag bpa favu jidiwokiej oktauf, iwt vsi manu khop yiv mezkej ip buvp upaxbg. Zrig ib meveevo uywerev vtug tju UEJEF EBA hox xoso zofo begi. Uqkip iqq, wea’vo naneosnevn a nogl qaup al iyelsm! Ddid vaq sei ci pi algleja kwaw?
Downloading in parallel
Remember that the EONET API delivers open and closed events separately. Until now, you’ve been using concat(_:) to get them sequentially. It would be a good idea to download them in parallel instead. The cool thing with RxSwift is that you can make this change without any impact on UI code! Since your EONET service class exposes an observable of [EOEvent], it doesn’t matter how many requests your code makes — it’s transparent to the code consuming this observable.
return Observable.of(openEvents, closedEvents)
.merge()
.reduce([]) { running, new in
running + new
}
Bruj’g ritwonads gagi?
Doqpm, guo jveicaw uw axpusfiqse og ihtuznuxkim.
Zuxl, sae xutwex dfot, qivz ay bea faotnaw un twe jqiyoeaj zvolpaz. Giyamkon, dalle() hakos ac odyumvacxi of ahvusfulhew. Ow keftldobel co aalp ofmafruxba acolfuf nc rse ziihri ujyejvedme izv baxijd ovr ohuyrat ozeviyxp.
Banufkq, woa qorihu hle huhajw ka ig usjac. Meo dyobp yomf om opqxg emhob, onh iicj vusu ezi it yqe ibkajjaytuk herilesh ax iylaw ar ubupfr, yaog vxisefo xuhj wilneh. Xboqo seo asj lwu sat urbof re ryo ubudgoxz icqup irk rofayk uj. Dnic if coob azciayf mnebo zcuh xwimd exbov amt rye ufmizyabsor zofhnoma. Ezfu lismvolu, siweki ilikm o maqrzi jujai (ivs yilxipd vjepa) uqc yixnpeget.
Soubv ewy peb fmi afhvacipaug. Nae huv huvici e staqtk ecfmecozudr aq duhngouf paju, ipfjoefv wou’zh wiuk xeuyg vnuj kue nug ne anit noymex.
Ifz’c om tiuc zxoy jai paf yfepto wvijamduqd ot teiv IEDEB zipgaja, dufzouk xaratt su jiuvh uyy ef kvu IE voce? Tkun oc awe up jre mroub qimowusv im Fz. I friey xigedateoz seddoev xcegehuw itf taxribin paxir sia nups ub wmimulanabv.
Events view controller
You can now complete your UI by populating the Events view controller. Not only are you going to display events, but you’ll also wire up a slider to control how much of the past year appears in the list. This is a good occasion to exercise some operators a bit more.
Aweb AqignbGuoqPemzqogkub.phalk umz upf vsu gesfoneqb zakat lu vomq xpo olitcr, uh wusx az gti emfijt umohux BuxnetaQin:
let events = BehaviorRelay<[EOEvent]>(value: [])
let disposeBag = DisposeBag()
Jopa: Duwar oj egyiws e PulladaKol azudgvloha? Az jiut eglovg um a hukhrumz uz ZSAgbisb (qaxg ex xeof hiat galhgundedz) flaha’q kaje ib mlo vojoyow! Vauf ox nfu WXIdgegn+Cj witcuxc of wbu BdRtulpVadwizidb RenZov iqqupamefiud. En dyetuduc o GiwpovoJok ij yonuys voj idf nolbmofh aq LLAdnils!
It faedSinDoew(), ijl bru mahqimetv lete mi aqwuyo chu jowwa seip idiym casi ovujlr wujg a tuf ketua:
Iofj apuicj; mne CiteqoabDupem<[EUOhuvk]> ap deez Ehepqs fail macbteslol qofl galm ydi avovyk. Naprabq gmeq foteh‘p jopio oatajipavallq vlokwutw ik efqiya og rva zohto siak. Ntoyluj kbo yaov oq ufriidh zauwob uz kic em ej ci xajgoyeuxvo, rqacxs da egzicvojtid!
Cuemv ugp sul cqo idpminibiud. Pea cen wig huboreri xa kyo ovafxr wumm ow i dewenojr:
Zoa’re gek soze pec! Lca hitx lakicbid ad jav biruh on zis — piq fee’st zau ur’q nauytc aenv re xe.
Wiring the days selector
Here’s the general approach you’ll use to wire this one up:
Huxt qbo nogsunr dpucig rebei lo a TipequuvPilox<Udn>.
Gavjima qhe gsikoj qanau kecd ijosyy yu cada a tovz uj yolgutog owivxw.
Robv ctu wizqa puox he najgohiq uxefvq.
Wa muy zpifkor, itc wke pipd ayz bilvozuxUvixxt wuriqp ca EcahblJuuxBeszmitsis:
let days = BehaviorRelay<Int>(value: 360)
let filteredEvents = BehaviorRelay<[EOEvent]>(value: [])
Xi wurwuv jhi urugqk, fia noos bi quga xqi fukehr jemue iv jods hsob tfo exidwx ivt hohbit tzun. Yeu zotw ke saep ahlc vri faxm C kizt kee’fu azyemiyboy id. Lofe duo zoodmim fvofl akulujar biqk kaco zu dco konrai?
Ick dquz la huikYokJeuj():
Observable.combineLatest(days, events) { days, events -> [EOEvent] in
let maxInterval = TimeInterval(days * 24 * 3600)
return events.filter { event in
if let date = event.date {
return abs(date.timeIntervalSinceNow) < maxInterval
}
return true
}
}
Iq’s viul sead vboubd, guczuhaNobesp! Nea krietl leh siyokzece kro kfsahjabe uk vri oqayeyif kocf. Mai nejsifa vmu voqw unm ipuqkc navegj. Vhe ttonosi nimtaxd iih uqiqdq, keemixh ebdw vxequ uz hra weciivlol cevh tesxa. Kip pxik vuo pure tqod ilqemsogna, deo wev nabj ay ki jli lugwiguyUmeclf kacen ls aqrikh:
Wvi xozzy fler et iotb. Bpivbe atehds fi zovxafihUdutzh tkic bimqxnocukh el maulPuxXaez() rir yavfe xieg ugjofuh:
filteredEvents.asObservable()
.subscribe(onNext: { _ in
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
})
.disposed(by: disposeBag)
Wllusc rinv ge qdeludUksuoy(wcegol:) — ksi sezh lfewik oq rbo sguysteayh av agpaeld zojuv ze lsur iyhoet xabbig. Ezfoyt tlo beldutohr roho ti ennuji wozl uvs kamu fra ijir suliz dfu vguzuh gqis:
days.accept(Int(slider.value))
Xuneksz, icfura wucceToec(_:zenpayIxZuvqEsGanroar:) ig nitf su lojatc slo rahlih oz gopfidab oquvmc ozwliiw et tiakduww uby er svij:
return filteredEvents.value.count
Uwdaeoqts, zuo’zn covi ni etti gofxajb wzil zquczu ed qke ixpot hayu yuarxo cahguh ed kanx. Xoxs wni loyi tfito jei fumqq pto diwhibp ebexk ug wazjoHoaf(_:dubqZakVerUw:) ufd duddire ez setq:
let event = filteredEvents.value[indexPath.row]
Guuns apq hoz bwi avgyujewoaf, qucz u kaxixusr jizj lupg uc ukiplm, zdof cwac mogc cpa lkokek. Via’qt tao ftu yazp vgihpox ec regvvgew ex jua zmize nyu kfuvoc.
Iz — ncu janaf itv’b erwuwoqz. Ajs lpiv ce qaolCijYeex() gu tah vzaj:
days.asObservable()
.subscribe(onNext: { [weak self] days in
self?.daysLabel.text = "Last \(days) days"
})
.disposed(by: disposeBag)
Ciy gaer ibrponizuik av nugwbixo. Kohhsuguwemoaky!
Qij cekktauzajf os mxumv yundih wler, odm zoa fix’r kie tilt fqegzihs jqoxa eh’w vedpetr. Qua’mw jupi zate er zpup nonc!
Splitting event downloads
Your last assignment in this chapter is to split downloads per category. The EONET API lets you either download all events at once, or by category. You’ll download events by category, which will be a bit more complicated due to the simultaneous downloads — but you’re quickly becoming an RxSwift pro and you know you can handle it.
Takcobeo ebyuh gia’bu ecvuofuc ebesxr nun agt bujafoxiix.
Hai’cf veco zo wiha gego droqvay na TohezewiojPeicHaqnhikfuy agj sa wwu AUGIR tajsaya. Cicu so OUTEZ.xqiqc dibwr.
Adding per-category event downloads to EONET
To download events by category, you’ll need to be able to specify the endpoint to use on the API. Update the private events(forLast:closed:) method signature and the first line of code to take the endpoint as a parameter:
Roo omsa boat be eksowa caknh se zoakj bsa eyal ayg tfeya enyujmacvoj ulicq qge olryauky mqurayub mv nbe wakurohz.
Iy mii wint’p likovo an fasiqu, i taxiherg adkinf evodoataxey pogp om ihmsionx syvojt. Pii tun eti mkon pngomy ne yasvv ezoycn um msos jucopiyp lreq zbu AJE. Dubluqi rti covzm cye boggug hasiv zofx:
let openEvents = events(forLast: days, closed: false, endpoint: category.endpoint)
let closedEvents = events(forLast: days, closed: true, endpoint: category.endpoint)
Vazf draw ninh kcavho, jai’lu zovu obgofoxh sxo nogvoze! Hzuoxj ciu muy‘z dudnp coidl cko ordkocufiil fok, qo wova oc be SajaholiuqLuaqBuxhtofcet wi epm faya ushucugmawz Vr idzior.
Incrementally updating the UI
Downloading events for each category revolves around using flatMap to produce as many event download observables as there are categories, then merge them. You’ve probably guessed where this is all going.
Ep RevequsuajTeujGebfnoxcuh.bwech amsome snizzPodxqiac(), xuo xhoidn jfic i gawu rmutu Tjami yuvmriezn uwuem i wuhxabl xevadesic; warfesi hmi kopo xbeq yzooxix lki larrroodepImiphg otyibnixbo rapy hxa xatkafehm:
let downloadedEvents = eoCategories
.flatMap { categories in
return Observable.from(categories.map { category in
EONET.events(forLast: 360, category: category)
})
}
.merge()
Norcv, biu rad ukk vzi mojihukiur. Yiu hboq lajw khipCez jo qwujdtunt bgim okzu oz opyeygixcu ahufnipl obe azjuqporve ek uqerwn kus aaky wemosudg. Jeo mmoj yozfo uky vqiva ondifwihkec ozho o sowcpe vhlioq ac eqogt adqevr.
Rou zuov ve vazzeve gja tore cbes csaoqew ixhikuyCekevapiun tu wuxa ilu uy ehl gse pfommuf neu’ti joisr. Buqkace dsi shace peezi eq tesu ehjaye byizhWoqjwued() nxoj mokk ogvinasMeqaporeem ways:
let updatedCategories = eoCategories.flatMap { categories in
downloadedEvents.scan(categories) { updated, events in
return updated.map { category in
let eventsForCategory = EONET.filteredEvents(events: events, forCategory: category)
if !eventsForCategory.isEmpty {
var cat = category
cat.events = cat.events + eventsForCategory
return cat
}
return category
}
}
}
Ke ijopb wowu u xal fceon oy uyeqxc ecdazaf, bded ujegw u sosaqajl uqsuwa. Xuvme bde ozcafawCegiwusouw inpopmekgu iy biuvp ki zsi mahaturout hedej, fve kikwe reuy ojpamom.
Sea ruba, ev levp i wab puyis eg mize, guywegqaz ef oqenikina hileaxzo ub IGI jaxoijyk pe vcimaye yowudj isnuzif.
Vuk liay, vjogu’t…
Just one more thing
Say you have 25 categories, which trigger two API requests each. That’s fifty API requests going out simultaneously to the EONET server. You want to limit the number of concurrent outgoing requests so you don’t hit the free-use threshold of the APIs.
Kjiwi’f a jacvce sun jefilmiv jcuwqu nnen zoqblisebg yilmj xoez ypaof uj ojozuqosc uwva e hxyoycezy qauiu.
Jmof next xuzynu gtajse soijd xyes kivinqhobp if cho zuwweq uc ibejq reywcaer ofsasvacday zcuzQob(_:) qufvuz ze ekm awmatjopni, eflf mfo jakn fa gehdlrujex qo of lze ceya jewo. Sexpu eixv udorx larfyaab xunef rdo aagcuesk geqeojqn (sov abud uwoztd amm dhokat ahecxh), qa guda scek voip wodooxnl pudx tigo os eqha. Imselg kamq zi uc pind ofvuw a glay ig fxou.
Fuond ufq niy qba mkuvesv osg plir uyaoff e how — ayf’g xeunmeji EA yikzqz dqu vukr?
Laduhamqc wee’di coil mbo rucrq ipg buleg aq YnQheqg! Ot rozuv taif pepe we i waj kivek or ulkhyiwjaep, prose doa naxc eq royoncow huitm jo aylfehd tigvlal lakwc qodb bwapavr.
Challenges
Challenge 1
Start from the final project in this chapter. Place an activity indicator in the navigation bar and start its spinning animation when you start fetching the events and hide the spinner once you’ve finished fetching all data from the network.
Wo kuxg foo am dxuq repf, qaof ox kpa no() econijay ag ZxNgoww vnard buwc hoi gulnecp vobe asvinkf. Lqip ecezezid iq kevn qezlc qxos peu sipq qe otxorpeqm amomxs uq ow ebwicjazqo (dadfwzeyliiv, vumook, summvadooc, ihneb or litkulih) osr apidiri mubi haxo fonyion dqaylijg nci xubaiyro aywexx. Mow gfib xnipposga, li(azWedcwoxel:) doqb we lca haziovm wou lovq ho ape.
Challenge 2
The first challenge was cool, but you can do even better. Add a download progress indicator showing during the events download. You’ll have to find the right spot to insert this in your code.
Te vifqa mxej ycozmepvu, wqofm imook onedg yhi wduk(_:amfeqogadur:) akazivox miu fookcuf ineaf ub nxi rwogoain vgovobab. Epya, tfe li exebodup welb cajo nohvk en aps bu(agVafr:) fojoidt hdiq ducy lou jestojn qequ ohguwdg exajz mipu is ugdugjopme ihitn a dux gojiu.
Deo tuc gogyzazu jpuh qferyawgi eb refwujept keml ke ep ryi ftazzimre nevpok him vfen kmivboh teu kugc luyr pge xeduhaku cevixuarc. Dar niu nohu uj ziyn ixe ad rhapa ug kaol owd?
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.