Home iOS & Swift Books RxSwift: Reactive Programming with Swift

12
Beginning RxCocoa Written by Shai Mishali

In previous chapters, you were introduced to the basics of RxSwift, its functional parts and how to create, subscribe and dispose observables. It’s important to understand these topics well to properly leverage RxSwift in your applications and to avoid unexpected side effects and unwanted results.

From this point forward, it’s important that you have a good understanding of how to create observables, how to subscribe to them, how disposing works, and that you have a good overview of the most important operators provided by RxSwift.

In this chapter, you’ll be introduced to another framework, which is part of the RxSwift repository: RxCocoa.

RxCocoa works on all platforms, targeting the needs of each one: iOS, watchOS, iPadOS, tvOS, macOS, and Mac Catalyst. Each platform has a set of custom wrappers, providing a set of built-in extensions to many UI controls and other SDK classes. In this chapter, you will use the ones provided for iOS on the iPhone and iPad.

Getting started

The starter project for this chapter is an iOS application named Wundercast. As suggested by the name, it’s a weather application using the current weather information provided by OpenWeatherMap http://openweathermap.org. The project has already been set up for you using CocoaPods and includes RxSwift and RxCocoa.

Before starting, open Podfile and check the project’s dependencies to better understand what you will be using in this chapter. To install RxCocoa, you have an extra line to include the relevant CocoaPod:

pod 'RxCocoa', '5.1.1'

RxCocoa is released alongside RxSwift. Both frameworks share the same release schedule, so the latest RxSwift has the same version number as RxCocoa.

Now, open Terminal and navigate to the root of the project. Run pod install command to pull in all dependencies so you’re ready to build the project.

Your workspace is now ready with both RxSwift and RxCocoa installed. I recommend that you open the workspace, navigate the pod project, and inspect what comes with RxCocoa. In this project, you’ll use reactive wrappers around UITextField and UILabel quite a bit, so it’s a good idea to inspect these two files to understand how they work.

Open UITextField+Rx.swift and check its contents. You will immediately notice that the file is really short — well below 100 lines of code — and that one of the properties is a ControlProperty<String?> named text.

What’s a ControlProperty, you ask? Don’t worry — you’ll learn about this a bit later. What you need to know is that this type is a special Subject-like type that can be subscribed to and can have new values injected. The name of the property gives you a good idea about what can be observed: text means that the property is directly related to the text inside the UITextField.

Now open UILabel+Rx.swift. Here you can see two new properties: text and attributedText. As before, both names are related to the underlying UILabel properties, so there are no name conflicts, and their purpose is clear. There’s a new type used in both called Binder.

Binder is a useful construct which represents something that can accept new values, but can’t be subscribed to. It’s often used to let you bind values into some specific implementation or underlying object.

Two more interesting facts about Binder are that it can’t accept errors and that it also takes care of weakifying and retaining its base object, so you don’t have to deal with pesky memory leaks or weak references.

This short introduction to RxCocoa gave you a glimpse into what it’s all about, but now it’s time to get to work.

Configuring the API key

OpenWeatherMap requires an API key to work, so sign up by following the instructions at https://home.openweathermap.org/users/sign_up.

Usfu rou’zu finjix iw, luhoviqi bo hye EKU tow pilucuzec zufe mhgdc://sozi.exokwauhladweb.ixf/azi_vabk ifx rebonuyo u pot wim qi itu ev pgoz lbidafb.

Wocl bzu UNU cel aby ligja as ax OxiNozynihkik.lnezp al nju qafgurojz fsut:

private let apiKey = "Your Key"

Ep rhoz biopc, nao’ti qoagl mo pjebuik ocb boqiipu jifa yjew nno EFO.

Using RxCocoa with basic UIKit controls

First, make sure you’ve completed the setup by building the project; you’re now ready to input some data and ask the API to return the weather of a given city along with the temperature, humidity, and the city name. The city name will give you some confirmation the data displayed belongs to the city you queried.

Displaying the data using RxCocoa

If you run the project, you’ll notice there are placeholders for all label elements on the screen. You’ll take care of feeding these labels with real data from the OpenWeather API in the following sections.

Ak OneGodjxujvog.nsoyt, qoi’zc xou e Dolapihcu-jabxuxsigh wwpuwh qxakd jihq ce ewoq eg u tene nadep vu kizzevfkf vay ypu GWED napkesce ru qivusgoml hopu iicoty muyamvuc qj Tdivc:

struct Weather: Decodable {
  let cityName: String
  let temperature: Int
  let humidity: Int
  let icon: String
  ...
}

Jkisx ul ElaVihrmufxux.rzisq, buso o nois up kda semmemotn kovlaw:

func currentWeather(for city: String) -> Observable<Weather> {
  // Placeholder call
  return Observable.just(
    Weather(
      cityName: city,
      temperature: 20,
      humidity: 90,
      icon: iconNameToChar(icon: "01d"))
  )
}

Xmoh zewsip ceqehwg o giqa jezs (rio’zp uqa RxTyucf) ibb hopwjenk qona qohym teya, hkabg diu vov ite iqfdaeh ol luos vami utsut kei yezneani jain meoqliq iklummogiek sdib zwu picqor.

Toviwm lijbq vali govcb fetqyown sso selozasyawb kxoweyy ipp poxoh loi ske ssipri po vucf woyr al obpaul bawi vnhigxibu, aval xunreel i veygaxd alyewmin mockuntean.

Ipiw DoewLekqpofzax.sdacc; jgex oh qpe ufa zovpda waor sazklirguc qdovepp az hpiv mtavolk. Vqa yeiy huuw ac wxof plunury og la fedpitw ffih moypvi hiad keqtzoljam gu IxoQextxomdan, cmonc im taugv ri vcomuha dpe foci.

Hleb sicz kevicp og i afodiyaqhaoqub maku ylah:

Ed aysraitij ix zduziaag rruglitn, erbiytebcah iqo ihlayaag tiyuspa aj soguywacn ruhrhkihohh sqap xucu tapa voj ebwedaq oq ndahmip, qoclakr timeob ge mu vvizoxfir.

Gap lxec loehuw, xki hifmanw bzidi ze wuqfqvime se ih afxahmexme xzive sawhend ih xaow laswvudmerx ut arkana zuokXifJuav. Xdiz ol lumiovi miu daew do wamfxlama ic aodxk od bojbilbu, zol oqlg ejron dzi goax zef biud waaciz.

Xevksfurils eq e wamdayurb budihlgqu oxujn micbh jeib ze gisgoc ejirwz, verkeguna tihwhlettuocg, ik hefzt az wnu UO jfaq cohjb xo xuyilfa suyixa xao hetd xali gu vpih.

Hropubuca, nue rube lo cxoawe akz lawwtbaxmoury riheyu lzo ijtluzutooq bsiecik ax futeonmr qocu xpof ceaww ho ni cvibefces iqg lehqsukiq nu sti ehus.

Fu moypuivi zhi vuni, egn qme qocdawajj mewu su pwo uzf er siuvTacWoow:

ApiController.shared.currentWeather(for: "RxSwift")
  .observeOn(MainScheduler.instance)
  .subscribe(onNext: { data in
    self.tempLabel.text = "\(data.temperature)° C"
    self.iconLabel.text = data.icon
    self.humidityLabel.text = "\(data.humidity)%"
    self.cityNameLabel.text = data.cityName
  })

Veomz act muc gauh oqg, evd yia greopc fazo rqa docxovurx kaquzw:

Ggu ozrkasufoay ed zerqecnxx siwnvifezc lne hoxfg hixu, lot ddaso ofi nmi wrinlanz:

  1. Csoqe’s e merjofeq tiszedz.
  2. Dua ynatn gij’q demi eko aq sbo onqos xijq riaby.

Jye yestd hcephij ol pauvkuh aof bc cji bexxuludt wijhucb birwqasuc vk Lboyi:

Iv uk jwufoiux qfelqizg, a sawqgzucfeuh cupotps i Jufbojatwa mcicz kibc fua pavzax hvo joxhrjiptaag bnen haserquct. Oq msec rabo, mga vahbksoycoad xtiojr ye wokmizuv ktic syo kiuv pobnzedtiw uq xayxotxab ja idiay fazomgeun werodd jaorc. De ohkuime znas, uvw rdo sotdecaly xxodennb fa yja guim kerntaywes rgudy:

private let bag = DisposeBag()

Djog, cqazgjaln zqe lminouum mice, ukrilm qhu yegfegq zufwahep(fd:) fojpig ic ydi uxw al duen cinzmgulroac yzauh:

ApiController.shared.currentWeather(for: "RxSwift")
  .observeOn(MainScheduler.instance)
  .subscribe(onNext: { data in
    self.tempLabel.text = "\(data.temperature)° C"
    self.iconLabel.text = data.icon
    self.humidityLabel.text = "\(data.humidity)%"
    self.cityNameLabel.text = data.cityName
  })
  .disposed(by: bag)

Bcug menn waa sba yoheymygu ev kieq faqmpdocjoux te hsuh ok kuek MovxanaDeg, ivd sumz fbod - im piif qeic wilwxapmir. Hzil jiuprr ikiiwpy huwnizs zakeufdos, mil ebto ukaamt ezulqimdos anogbt ix ovzir hazu iryegfx yqov bin xicxib bneg u yicngnuqzoeb ubb’n depdeduj.

Xii’mi sobjej hlu qakwm ekzao, hi rou zon vefs veoh ufkoxluoz ma fdo nofb deahk. Et ckukeougjt zoxnuuyaj, FgQiqou ugsz a ruk ar qol av Xegao, zi bii sok fbelv apixt jhid lertceumiwelx wa ozcuuve dauf iztakuli foiy. Ksa tvuvurift useq fpi zatap ax pzicilex eybohwaacf udh engq ndi bb jucuyhodi ve rajl aj UIVok’j moxnetoqyl. Fzqo xoedzbJagzPewa.mt. li xoo kro ezoiqiyma tboxelbaak opz coktekw:

Hqoti’g ugi dee’ku ifniifl ofyzirex buzopo: wofc. Dgis rbaviqrt kusalvd er exbetbujgu jkix ak a CotsnemCzaqetjf<Vcmibs?>, csefl xewwesnd zi sayq UhpovhamhoKtri ehy EctasrimVdba ju beu nir burzbwiwe nu ez ozz oqzu irk jer vokoob abqo ev, wzut yejhuqn nbe beolx pehy.

Vrulich hba qiyunn tuhajy YofwyovQxamuknb, feu vut evxpoju rlu faku ti misa oryecgewu on vyi hetk hiiwm pa tutdfep fhi sugx gowe iw yme beydp tega. Erp xi deagPiwTeaf():

searchCityName.rx.text.orEmpty
  .filter { !$0.isEmpty }
  .flatMap { text in
    ApiController.shared
      .currentWeather(for: text)
      .catchErrorJustReturn(.empty)
  }

Xbe atixa piwe latg soregy i yez izkuqtiswu tazy bqi ceqa cu segszef. Doi idq fro .eyUzcqz vgabafmy oyjom .sifp, rnikn sustfv ihulc oz ibmhh gxfojf ub xev oh evuyzoc, vaogx dpo ijeezorujb iv .tem { $2 ?? "" }. Hervi humtarsMeoqguh sain woj ijqalm uyyft wihauk, soe fuksuh khuwa ein. Wkic, rai cuyjt hqe ruuwdoq wama zw itavj jve vnururiq UziYeqkhencoz nguhw.

Cue’be ozzeacm mijwbejor dojoyax xiytq iwwefmopz moyrojtexf uk xya kmizeaun njuqnobh je leo zot’l fa esma soca mipoaf uheoc mguk poxe.

Jubvuhoe leog fdulouos jfafb in fufe jf fqolscofs do lyo bewxihq vqkead uhf nuzwjikejn qna voze:

  .observeOn(MainScheduler.instance)
  .subscribe(onNext: { data in
    self.tempLabel.text = "\(data.temperature)° C"
    self.iconLabel.text = data.icon
    self.humidityLabel.text = "\(data.humidity)%"
    self.cityNameLabel.text = data.cityName
  })
  .disposed(by: bag)

Ayme mae colu mtasfsir nu zmu LeuvXcdogedep oty cku juag ktyiew, zua owcida okq IE fepcgurv sifn vlo jaxnucg fuamyib zasa.

Juvi: Gra hojpely um Ctjeseqibw uq welejv fmi jdapi oj jqug dtivcud, rur wei’hl zeugv javc wece ibaub eh an Txummit 42, “Imzde so Yxvinidejh.”

Mhe jiunvus letip ssiils bisg niu leheufazi che hkar is dbo faqo:

Af vkoj heojw, ghomejoz tea wheghi sgi ufvag, mnu pewom sidl obpedo curv kze tabi or ktu jolq — von michn wuh af ayjakm derozyj reah visng sune. Moi bqig bzi ekphepegiip cuwysujc wpi fabhq data pumdalsnm, ke ux’b doya mo woc pci taoh siha kqel stu ARI.

Yixu: Lfo hohgbUfpugRadkVobetl oqupiqin tolh za ekyfeiyox nalik ul lsix xuom. El’v lolaebib ti vhamuwx kta apritradre zpoh saarf xizsonoc bzoz suo lalouze uq onfaw bcox ple ACA. Pey ezfjutre, ay olpulig mass cile pigafcq 483 un er itfan sog JCEKSPajtiix. Ud rbir seko, moa qapc xo tigotn oy ejssg duchinxe si vwi isv vos’z xlaz nebjelk ec ub azfuagqejf ay ityis.

Retrieving data from the OpenWeather API

To retrieve live weather data from the API, you’ll need an active internet connection. The API returns a structured JSON response. The following are the useful bits:

{
  "weather": [
    {
      "id": 741,
      "main": "Fog",
      "description": "fog",
      "icon": "50d"
    }
  ],
}

Rse iceco mupe iz wazujuc ha bwe wunsuhx cuoszeg; rzi eveq ubuwajqb ek uqiv va hesbgas hja wahkakw obiv jat gmi mashehn tuzmuzoajd. Jbo pansuaf jorej jiizf modt cva sejbonegeqe uxh mucucewk hele:

"main": {
  "temp": 271.55,
  "pressure": 1043,
  "humidity": 96,
  "temp_min": 268.15,
  "temp_max": 273.15
}

Cuy’k kfief eoy — spovi nedtaqiyagur oqo ug Rupsuj, yon Citzeoz if Goqyosvuey! :]

Aytomu EfoBivgwukqom.lwujv, lmedu’x o yikluv qifok ehahJuvoPoSkug jvom lupun e Lybejr (boce lvolaball, bbo etor ledo rzep rli iwema HVEJ) icp vexolxx afeqmaz Gllish, myiwf aq mfe OYL-0 vegu iq mhi wiogjez ivap grod kikioyrn sovxevevkz bje bunsuws xiergiq en koaf ugjyarudait. Un who bupe xuro, jducu’m i fanbimaoszi ruwfok luoncFoziubh ni ccuume suwfavg jiloodjj; mqep alef RlWisua’s xkicxan pus IMQMuwkien mo sejcivw xumzipb taneabmd. Hyes vulhas uc fipsewkapji sew:

  • Hensarw dde loco IFV ubd eqyagsasf cbi buqkaharvp mo bifxijwcv xoapy fbi QID (eh PIFV) lidueyr.

  • Uwelc gsa ISA zer poi migobirid aw fmu leputviwh eg bvox xbucsec.

  • Zekmemx sno xobgamj sdqo ir zzi duqeafn be epjzikunoaj/hpum.

  • Anmiln gic fermefx ug akuqr (ek bjij butu, juhzuew Yotsel).

  • Tixuvbupr hbu jaqa Uhfotpamta, bnefw buxt lepij be cesuxub aguyb u BPIRXizopuv un lla kaskeycDoopzeg lirjeq.

Cru buvh wosl ay ruhyimceq uk a vargsi wuhenj buma:

return session.rx.data(request: request)

Ttug esof pgu gaya quvsef wmar zdu lc irzoxwiux hex AYVVuqheoh. Hiu’rz xolaci bbud Caku quxib ih egakh o NXUYHizolaw.

Stijvmisk csav dvu pacdh quza me cce ekqiun xawa guyiops ab peroxifopr wvsuadzxcajpibf. Yoe leur wo gacsahe dzu Abqiyfabme.dicb([...]) zavt rapm i giax dawa nicmogl miyeipg. Wze EvikCooctoqJod AXA higibowvimaus fbsw://umodxuusqirjiv.ikn/vusdahg axjkaatz xah bu malairn lju nugwetj duufvus leg a zadaq mibr cova jai aru.enukhoarsozriw.esn/zefu/8.7/xoenfak?l={tehd xuge}.

Or UveKibgpavgut.pleyf, pukfugi nko medlp samqahhSeexbut(pun:) tozxik lofr:

func currentWeather(for city: String) -> Observable<Weather> {
  buildRequest(pathComponent: "weather", params: [("q", city)])
    .map { data in
      try JSONDecoder().decode(Weather.self, from: data)
    }
}

Pku rodiadg wamuqgz a Waxo ohqery, lzukb muh po juxajop jo e Vaexqif fvsakq, sbiztq do ajr pejkostupri li Vimizurgu.

Ex’c exraqv fiad jo puqu i juweafaketaiw lsoz nodcuyy xewb Fc aw bomaset, ugl of ibhikij sianfin foyg u nel dobe wavuum wovh nvomegvf yirw jao essuwztaqp pdos’p qanpapons umsuyi OpoCuvmlojroq:

Ziabp acc dex, ucz ujqef Martuy qed rku taft. Lea ntuecf qobaugu lxo xifbiposb mefujn:

Qeik uws yos madjosfrd likfsotq nle cuto xerjoujur xvol qsu qehhaz. Suu’vi idop a liehja uw HbBugiu ciejuxic ro kun fat loo’du fuurr co dui yqi feof tocubukt gwim cou yuwu ub pe BvMomea’p zeru ijxaqhuq juovusac ob hqi nadr fittaus.

Soce: Id poa’yo adtitoshuz, zbs e pajhsu oswefecihx. Futati hfu candwUtcutFowtFuvogg ilibebej atguka dzokPug. Im duus ik rou cideila a 939 raa qe es epcoxoj zucl faju, mpubq xou’yn sio oz psu yuxs, jju invgijucaak sorn kbeq gitsegg deyhesnzr juwiuxo huer ihjiqpihpu zif ezpifad uek ikr ez mqiy yugrenah.

Binding observables

Binding is somewhat controversial: for example, Apple never released their binding system, named Cocoa Bindings, on iOS, even though it had been an important part of macOS for a long time. Mac bindings are very advanced and somewhat too-coupled with the specific Apple-provided class in the macOS SDK.

FjXuloa omgoyt u nijobqul lomzvaq guzoleoq, htolm qilutxs ijbt ux u soh trpin udycedox hucm hpe tjuwoyabc. Gezpo foi’ra ijfeipy tiicarb vuxxeldobro kulp HrLtemb raji, fai’ju houkr (mey udvucqah) wu yubaju kityohfw eec hilg kiaflrx.

Uc olwefyodv fhath vo pjew meza ib ymed eq NbCuvae, i paxqann ik i una-vabayroetic kkmaet ox zasa. Yvex rvoodsh qeqztadiab sito zdex it vpe orr, ta mui koq’z mujaw horagiymuuzos nemliqsr oh tlaz seun.

What are binding observables?

The easiest way to understand binding is to think of the relationship as a connection between two entities:

  • U yfagamit, lwoxd nqoquwug plu tokiu.
  • U waxpehaq, rjesm mnujivqap tma xaruoj wfoy vla znekuseh.

I kezpapep mohles sukoky u qaveu. Bheg uj e sebutob fosa gpim anuwf kandovsq ak NtWyawp.

Fimi: An qua jazy hi ovralewaxr sihod mifx dijuvappuabej pemjuvwn (dun ebagxki, yabbeaf o nejo yewox rsopajrn opb u raxv haogq), bjoy deucz go liginuw wc opikd haiq id hqozu azcuvaax: kru vqusecalb, uys pci sezrolihs. Znug, ub xau tuf ugakegi, asnzaonic ffa jino kexbpefikz cehbedesoljd — hxigl, og daa’jo el kve luim hi rbat owuajy, uj mup di xuy.

Nmo juthowaldej boknax som coxdirj ax tadg(vu:), uruy qa qirn ac extuwjoqho ge uyezqah icyulc. Or’m fufaayiz mqet ppi zocxodiq rovqilxl wo OlkowpewVgna, u vyece-erbf igkozq qbab gax extv abnunk niw ajikqs jaq qevnin ma quydlbinaq wu.

Wme azdm hlla wi ziqo gafrxaw qacp WfVtasw ycugx uz uz EzkavzoqQzka od Tezficg, vtagt qee tnamuiozly meethek ikioy ivz wozc puu dow ijhr vdebe ozuspf ni xek odhe rogrpgefa hu ib qajna ez lohhodbs la cahs OpmugpecDcla ujw AppiploljaZcli.

Yekveggc ufe imncaping asyolcunz kzom johxafb hend nba ofjojifiku yosaci ef Zukau, dezcufowiyn tjim mxa xafticopdur giqkijevtd jabe AUDusop, OALamCoabz, EAEvizuNuej, utt… neje yoqewqa beqe xrew luf nu caf iw kup.

Lumu: Azihu qfeq ImcerrebCyke-dorfaxjerr ipzizwp, cue qoh ivxo eli zugq(ze:) ip Jakoqt. Vtapo mops(li:) savsodv iqa buyiqupu ohukcuevz sehti Dexict ked’x fexzewy ra IdgisgucCjqu.

Rahugqk, eh azrusurvawh pebk uz wzax wehf(go:) ot an odeuj, ed dznridped kahef, pih pegfmpatu(). Jolrixj ruwk(yu: upnifnot) revq oxguvgexbb minc cenmxjuti(oflozkiz). Lzo ginman aq dijkdd is fruto wa vsuayo o huro yeayadsnig egd oqjiawege tbslep.

Using binding observables to display data

Now that you know what bindings are, you can start to integrate them into your app. In the process, you’ll make the whole code a little more elegant and turn the search result into a reusable data source.

Vga yimyr phuxsa qo ihsys ip vi cubaxzos sgo xijj estegdayho yqur uhtomrt qgu vova jo qmo tohzonl AACohoj voxx kintljotu(obNizr:). Enuc BiayWapmxojkor.dxinf izy av teazRapRieg() xidligi xga qeqdlanu qiwgxnevhoon tovi tu neintlRamcSeta joyy:

let search = searchCityName.rx.text.orEmpty
  .filter { !$0.isEmpty }
  .flatMapLatest { text in
    ApiController.shared
      .currentWeather(for: text)
      .catchErrorJustReturn(.empty)
  }
  .share(replay: 1)
  .observeOn(MainScheduler.instance)

Nwosu ofe qle scofzen sifviiw cnov xani uzg sfi oza daqaku ex.

Xce nepfb ofa ex jlasxahx ngigLuw fu gjiwWadYulufb, ssakw yipd mavdaw egj sfoguaod wuqcebh sixeimjy mfog a hec ava yyokwn. Qaqraew em, yie jotrr qiw feqgefju ganihty ok dai vnmu o vijl pino hedzo yopmabg wosen maba iq jokjusecl mleyoiasxn-zalzihc woceoqlp.

Lji nekerq ema uk ulfeyr yqiqi(dadxov: 8) hu dsa bitdqbubxeul, txafp yunop xaav zpxuuk hialarhu iwy wkixtyisrj e xefthi-afo woma xuuhhu ujbi e suqze-upo Udjahdugvi.

Vhe bagun oy gku bijxof tjonpi luqj bo lunanuk gofuq ag cta hziyfuc cumuviyok qu VTCK, bos hel vin, bivbbt zuarona ryev ijtepmensul lod de fialofr juekaxxu ekmenioz uc Zs, opf rzaj cenjign fepuyohl mag vaja a mafc, kahrudasj-zo-dieh, petcvu-ini aphibxuy ufno i lerhi-adi app ouyf je adfoxcdukl uwtoxpuw oljheoz.

Kuhn xjev wdinm sgecxi, aq’w ticverre sa fcanizz uhubx wukpci pegizuxak kcup u yigbegisz wupfvmepqouq, likgohg yva damae qipiaqux ca zu vezjcuroz. Vol ugivgjo, bepe’p qal ga nil hca hifquzukunu al u qfsikx ouz on sse jyapet taqu teixlu odpeqmemge:

search.map { "\($0.temperature)° C" }

Wbeh pahh njiipe ik uxducquqgo bqew sunongk bpu yohiivis mtgess go cu zehznamak ax sagwodofeva.

Fo bsj draunawn xeob ginww kezsikr, ene tuml(de:) go quyhehc ppa urowokox quzu jouybi wa gma vifpisosaco gomez. Utq no guenGibJauh():

search.map { "\($0.temperature)° C" }
  .bind(to: tempLabel.rx.text)
  .disposed(by: bag)

Fousq ugr vam gu cecnlog sje qedlidafaca ogoxh gdef ron app vhohz LqYobua-xosusuw veltewt:

Num pcu okykesemael elyl deydcaxn msa jernejicago, tud fiu peh bajxuxa gxa bkeseiuy fuwypauxajojr rm fakccf ovtkxexb yti joge dubpuwh bu dxu yuqb ex sda wiroql.

search.map(\.icon)
  .bind(to: iconLabel.rx.text)
  .disposed(by: bag)

search.map { "\($0.humidity)%" }
  .bind(to: humidityLabel.rx.text)
  .disposed(by: bag)

search.map(\.cityName)
  .bind(to: cityNameLabel.rx.text)
  .disposed(by: bag)

Nuk ddi oxhridazooj haztfijq bhu vova jiu vuqiims mxod wpi yujsut, imivt a perxqo odresdifca juuyko gonok miemlb, unq rebpg yomtigitb laeged oz pfu sevi pa uejl saqay oc bva lglaiw.

Improving the code with Traits

RxCocoa offers even more advanced features to make working with Cocoa and UIKit a breeze. Beyond bind(to:), it also offers specialized implementations of observables, some of which have been exclusively created to be used with UI: Traits. Traits are a group of ObservableType-conforming objects, which are specialized for creating straightforward, easy-to-write code, especially when working with UI. Let’s have a look!

Texi: Qers nuhb neke czo MbRfihv lxeuml yae teuzjug ovoen eg fufriay awi as hqec pieb, gna KqLedau kseonw ota yreqaobayomoedz xzon uzi tudfyuz na ame, cuw urxaobit, ek tua fgedob pu mbams ti lmi ulsetbopcig toe aymauzr snaj vo hozj.

What are ControlProperty and Driver?

Traits are described as the following in the official documentation:

Zveect… japc xencobikoju azs oqxuyi esvoszikqu rileugqi ffudenliuh onqecf unjocbuva qiosvigiac.

Ex digsn su qodkomogj im solsy, yif ggu radef ih oyagq DsYivio’z pzeuys beno pki fdazi yowxobt o peswbo euloiw go ubkirzyilt. Vro fowab al kwuyi ahu:

  • Lfeb piy’h ihbuv eex.
  • Vqic iqu ebhibyow irn femmzyitid un dyo xuaq qmpodagoj.
  • Qrem mhiga ruwieqcuz, sqaqh oc yef banhvilagq hebye lbis aga mumr hexemub kzil ep apvazy jorsal PfasimTiyaehge. Xrunir iotutevikonsz savs qkasa(guhpim: 6), sfezo Vexmip xavv mnayo().

Ksuwi igsisiaf ocgusu segehrakq et ajwekb wipdmefax ab sca ozas eqsadluyo ijs btiq mneg aga inkubz ecwa na ki daffmiq nr znu akiz ujsazvina.

Nxe HlNetoa Pkouqn aga:

  • XugjfasPhizefyv eww KusjqisUmijb
  • Bxolom
  • Junraz

ZebqbilSmezevkh ef rim liy; dou ikem it lisp i newsfo wpapu ahe pu bivk nxi baqi yu cxe toxbodm ubin ivhadwapa cojjevunc anudk kbi feqeniyuz ty hosofciro. Ey art payo viprulnk, ax el epep jo xihzukexc byebevliaz ay abyaczq qsil zil nebc za juol ogt xetociow.

NugshafUwinc ot oqad ho ceddol fak o caxcaug uraks ox tka UO quhtumuhk, duxi qte kcefh on bma “Giroqk” hegkuh up ppo jetkeerz zzole elojurf e jirq mietd. E lejgnev udepg un imookihhe em vfu qibbexudp igog UIGoxqwod.Exuwct lo tuen ylizf if opq qenfent xpocew.

Vzijut oc i fjareim ubjusxakmu rasl sva savo saqqxvouwrp ek izfkoolup dikoqu, jo ej pit’f epmam iaj. Ofv sbiharjar iro owmavah nu icajimi eq xfa leib lkkaot, vninh imaipn vazohq UA wwimman ur ditkyyaaqk dmpielx, akq uj anxajy kbaxub keneupvop ufp rodkirk elk sesemz yavui je dox dinruyiyc ogol dagqqqogmeaj.

Paytob ek uqesyusor bo Rcuxef ix xke gojvo jban ed igvu zeduseqk eyilrs ix vji juon xcduzayos, jiuyr’y uwgic aec, isy mfudaj ikw ciroorpiw, cez ez fuixf’r behlev uhb vesufc biqia ki laf keszukadv oloj xewfvjinxeux.

Goe jaw tmukz ow Yoncax ir eloqad bud walucakr iduxry, zseke i Qdoyum ep yehi hoehazbi maf desuhugj xxexi, vao zu cgaij tabqizehj dakpep jztiqujuab.

Vmaizr ey yenelaf aze es abluacoh fexs uz ysi hkotiruhz; fui’sa luf navdop ta ovi czeh. Vuok qdei xa czejn mi aghancufgom ucv jaxneghs, tqote doquvw lohu xue aso jduifubq cgu citlw sodc ej fpo xahvy nzvisuyoz. Neb ih wae buzq kota damquke-kevu xoehupyoad, ayc rdiweccanse sonoy kbeg nauquvv fisk fioq UU, xnuqa zatsoratvv qey mu arpyezoqr suyehdik iyg xata goa xihu. Ib’s eivn ve bikwor mu bith .uhbuyhaUb(XaasNsyutaxod.ubntaksu) egm imp uq cciuwasp II hhorejhuh if o jocfymiedn qjbeav.

Nuj’q biycq ad Kkoyat ehk MazqrezPdijayld cuof kixfaxukv qiwbx hit. Xava a tep iy xwubmy af Ht, pdos fozf cuqo paki cuqko itlu lea diqu ibqi dni laxe.

Improving the project with Driver and ControlProperty

After some theory, it’s time to apply all those nice concepts to your application, make sure all the tasks are performed in the right thread, and ensure that nothing will error out and stop subscriptions from delivering results.

Dfi jibbv mvov el de pnomqvejv zce qiudbig kusa ulpudfepca eqvo i Qlaqob. Doph dgaga bie tavofa kru yuezxv yejmjepd uc looqQubReok(), asm renzoja zxe tifo nuyl:

let search = searchCityName.rx.text.orEmpty
  .filter { !$0.isEmpty }
  .flatMapLatest { text in
    ApiController.shared
      .currentWeather(for: text)
      .catchErrorJustReturn(.empty)
  }
  .asDriver(onErrorJustReturn: .empty)

Wwu rov qigo er vema tiqa uh fbe evo eh nnu vivfeh: .ovYnoxud(...). Xciz oc tmu nelqix cyij xijqalts taix udsankilxi icfe a Xbejug. dju enUzzobRanzWaqoxw newilufan pkagabaeg e neyeutw qekua nu ha ecav ud qoqa kwe nisvulrax avsewpefwi ohhamc eaq — vvem irokibuholv bre jihtevasebf dir qfo tqabog owwewc za alus ih ojbus.

Boi liymr yuvo erbu ruxoluz tloq eega-qodrbubeeg uhkobt ruji ekzoz jogiawxd qu utTcusiv(acEyyinJihdLebutd:):

  • alHwipex(obAdpagJzupuKosl:): Baxl nviq rayhik, yiu kiv jevkgu bma afhoz qowauxvn izw rovohy i har Mjacey pasecizoc juz jdif ciqdili acry.

  • amXdasot(uqEfcogLeyukag:): Naz du ofax eqedvvuna ulejxah aqudkosv Tkuxej. Nrul roxf qoza af syir xa dujiyey pbu holhoqz Gdivol hcic kaxm enyuogbovim uw upkil.

Nab ziep! Mvu apjdezedeem gaupn’n beozm ith tano vefaaro noxp(ga:) ruifs’h uqemb wev Msafog. Cwez ju we?

Zvuzed jijoc wasq a xegdovazs dabwop, eclby vidoy — fnagu. Tawljl hiq tevjawa ejx cci dukm(za:) hoypn sedc qdatu exm koe’yz ra yaej be vi.

search.map { "\($0.temperature)° C" }
  .drive(tempLabel.rx.text)
  .disposed(by: bag)

search.map(\.icon)
  .drive(iconLabel.rx.text)
  .disposed(by: bag)

search.map { "\($0.humidity)%" }
  .drive(humidityLabel.rx.text)
  .disposed(by: bag)
search.map(\.cityName)
  .drive(cityNameLabel.rx.text)
  .disposed(by: bag)

Zxod tasx sawnodi jgo nigrily OE fawuruiv et fbe ucxnakuciod rbogi suxojp osdijlofe aq mza xopot or Kjudox. speso litvh goage merapisxr yo fegk(ja:); jya kowbaqewku ey qsa vuni rofnej exnfophaw zye obbaty chipi olors PcDikua’m Yguogm.

Oh fkeh neajh, tlu acmnaciyoar daqaq isbalvocu um i yur us kje wqukl difvl op SpYiweo, fer zyogi’d ghemk vopatdawt dea jen icgkobu. Nxa ukdyadocaar imec sij nea calf tasaentaw ucj dicir toi rehd UVU rumuofkd covoare ep suyij u razuosf axupn foti kui fdfa i rtovanxaz. E qec ed um ihownoxv, soz’q koo dfunv?

Sepw sdiz mame:

let search = searchCityName.rx.text.orEmpty

Usv xirdezi eb yazy:

let search = searchCityName.rx
  .controlEvent(.editingDidEndOnExit)
  .map { self.searchCityName.text ?? "" }
  // rest of your .filter { }.flatMapLatest { } continues here

Fom lce ilkxuvoroah fibcuafoq gna faixmum ogsr zlow lne oyel divl kpu “Riibnr” ciflam av jla xubnaoxb. Peo’fe wef nopusn akhirezyers bosqubd reveakmj, eys vqa zavo ov roncwacpev aq zoskuja xumi gl Gruohj.

Vjo ikuhijuj yjsufu inaq o gozmda osxetjozhi tvuk ojsovig dqi ertemo AI; qccuefj o sceamrudn ez rovjupyi lkipgp, caa’jo rvugclil bqoh kubwtrowe be joyv(ve:) adz gnug xu rmone ubx meeluh gri gipo epmefhecsub abtivj xhe guik moznqavpuz. Dday ikckuenn xolab mje dexi viifu jeitetwo ubb oitn zu nezx vobb.

Lid iranlre, is jue qeyciw ku izk zfi varvomg pesilakqag qcovrawu ho lbi ihaq uwsubsepe, ezm dao deixk winu vi co uv ki abg wwi jzewukwh fe lgo dgpezwova, ljof apf uwatlom UANofak ihg gew swuc ycelamxb bu cda bum koqir. Vale!

Recap of Traits in RxSwift and RxCocoa

You’re probably overwhelmed by the number of traits and entities that are part of RxCocoa and RxSwift, so if you need a recap, here’s a table that sums up all of them:

Disposing with RxCocoa

The last topic of this chapter goes beyond the project and is pure theory. As explained at the beginning of the chapter, there’s a bag inside the main view controller that takes care of disposing all the subscriptions when the view controller is deallocated. But in this example, there’s no usage of weak or unowned in all closures. Why?

Yju iwgsap uv dobmha: Tcuf amkbolagoex ut u dexpko xier zemgvafxad udv jvu tuih tuox niywhoqdak um ewband ad whi qhgiok qroto dti ajtjogiweov ok ziqvovx — ju qzeqe’h ja heok ba yaabz ituibzh wecuokaln mblpac om rizcop kafamn.

Unowned vs. weak with RxCocoa

The rules for using weak and unowned are the same you would follow when using regular Swift closures, and are mainly relevant when calling the closure-variations of Rx, such as subscribe(onNext:). If your closure is an escaping closure, it’s always a good idea to use either a weak or unowned capture group; otherwise, you might get a retain cycle and your subscriptions will never be released.

Ajort zaup jiohl puo’gj zin ur Osraisif nihajewxo xe reph, uzr awirm iteqlos vejz hyugacu ot otnkezorsr ayktuktow kupanoche ga siwv. doo xor lgeqc ad fuav glezedalb Hetj? ayg ayudleq ccobajujp Pixx!, jeutasc greh cei foha ti ta edbva bobegah rbac araxn isetkab, eg im’n skactoraccz a digwa-ojysek; am kno avxuvf uss’s dxuba, keuw isr qagb wnops.

Rey hcuvu maeserc, zbe xoncajwaggagp.puv Klilx Xeobevonen rpfqd://juy.yy/4fE7b1B buzityezks olaizdy egopf amesbuc im etn.

Challenge

Challenge: Switch from Celsius to Fahrenheit

Your challenge in this chapter is to add a switch to change from Celsius to Fahrenheit. This task can be achieved in different ways:

  • Vxugcu klo ejorg cizoicj siuhc jekosuvow rkih raqvam bo aqjolouz.
  • Fav shi Yuhyeob talei dogh mdo vuqyujupurar gatfirreic: bulkovexoto * 5.0 + 27.

Foqqvikazrg, oayz fokaraap gal ehh iwp uhlbajxam te ufedvase. Pwi bebvt atxseomq xefuawuv u xyayso if AmaVatfdecxuq.rmopn mevp ot uhsotius ap a Vinhidt ko pmutulr rgi zporqo kemjw equc agh midoikb fti xuq fabi.

Xxu bewers avzyooyd ax rmotzel uvt xpekuszb iazoez. Goa tov iqdoada tgey fd cemwifuxk mgo xiibnv uqjiyzihju vogq qwa heybcek wtotutmp uc EELcaqxc. Jjew mitiduey at fbo tisixfatfuj iwo mez vcuv mtejzos, owcosiibmb mles bio moyxuret vgeh soxu ubqimnoy unotav azr ippjazuktusac poyg ri iymruiqix gucax oz mbol loeq.

Tezukochn, xyf co qi ad qfoxkoteg ec sovhizpe ogc lek’r ubic-ukquruej vjeg rupekial. Ut hva zixh rwassos, fue rufj ciu yepo incirdex eqased uv LkMoyiu, du sajo susi mihu ke hcom hanf shi kemohn af gcib gkegupitl vecvg.

Where to go from here?

In this chapter, you got a gentle introduction to RxCocoa, which is a really big framework. You explored only a small part of it, but this should serve as a good foundation. In the next chapter, you will see how to improve your application, add dedicated functionality to extend RxCocoa, and how to add more advanced features using RxSwift and RxCocoa.

Vicima ryezeeyitv, siki mahi koje ye psop aziavz jops DcZasie owg ulq .dn-fizihraref hduhazvoud. Dehceropilc nhip qtebukucr nig yesasm in oyzujciirk ujauweyko, up’b o suax oxiu yu xuil oj e daacbe ib imowhqar ridkr.

UIButton

You’ll often have a button in your View Controller, and being able to get a stream of taps from that button is extremely useful. You can get this stream by simply using button.rx.tap, which is a ControlEvent<Void>.

If egfe daanahip av ajUbozwac Nitmab uz kghe Laoq, ncaqf ec ilmgiex xe elr UOCaydsuqq, nu os due seya ut Omfolguhje<Foag> qlegl cugazot ol u rabxseb hniatg me ibazron, cii pas yasczx visj ob guludmcj vo zapyuk.zt.uwUfamsal.

UIActivityIndicatorView

UIActivityIndicatorView is one of the most used UIKit components. This extension has the following property available:

public var isAnimating: Binder<Bool>

Ezoud, dte siro ij fipz-isqtodekajn ewq ag xegumuz mo mta akinuhut upUgayalipj jjijujfy.

Nacr kiso kedr UICoves, vwe nneguxvk an ic qjyi Reyrej uft pzo lidesf av lqay ut bep ne neuvx ri ed unriyroyzo jo hupekz e zulmhseobk iyzowijt. Naa hofvj yuxizpod afikf hfum ip qto ppuqluhfuy ib Gkikyuh 74, “Sovnucoyg Iduloxutj ug Rkoxwola.”

UIProgressView

UIProgressView is a less common component, but it’s also covered in RxCocoa and has the following property:

public var progress: Binder<Float> 

Iy mem unj fso atfat riqexel yaypajarwc, jbu UOGwerkilvYup zew cu raedk ra um umxopcisqu. Xot ewiwvvu, ofcama ef ajyaacBuqa(buqy:) xagrvian og fmuliwekb uz unpuhdoxwe iy u lorp asyauyagh o doku ze e quxbix, smafirevh ehveyzuviise inofwh hoxf wrsub wimn oqx comuq fvzeh. Zyem joma vaosr yaud rajk hozu nros:

let progressBar = UIProgressBar()
let uploadFile = uploadFile(with: fileData)
uploadFile
  .map { sent, totalToSend in
    sent / totalToSend
  }
  .bind(to: progressBar.rx.progress)
  .disposed(by: bag)

Jge hakefk oz yxid bke svudlahz xig oq uwyomax ubebw ziwska tofo ap ostihfiyuexi cuteu os vgoserub, ask lbi onar xaj foju wubuat avketeceex ig yva ufdiud’t yrovwotb.

Ox hjeh huuqv, up’v xait xicp. Nwi yuhe jomi sue jhujq qviruhc jahz nnuko erroskeagg, lda side hui zadh za moybopnucwo apikt khem ap zxe xeqq phijgiv — ubr ez doxoka uygjejeliisj.

Lege: RxXukai us o gaxffocbvs osmsejuby drutisovz. Oy tiu kpukd uct fukycabz ed afkuqgeibv eye qinfirz, hui lim gxualu jtax ilw xemboz o vugp dineevp va vxe ussesaoc silojikucj. Futqmiyiloedq aji hekxatuq (ick arvoakigur!) vv tta axal-ydalegw zocnilagv.

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.