After being introduced to RxSwift, RxCocoa, and learning how to create tests, you have only scratched the surface with how to create extensions using RxSwift on top of frameworks created by Apple or by third parties. Wrapping an Apple or third party framework’s component was introduced in the chapter about RxCocoa, so you’ll extend your learning as you work your way through this chapter’s project.
In this chapter, you will create an extension to URLSession to manage the communication with an endpoint, as well as managing the cache and other things which are commonly part of a regular application. This example is pedagogical; if you want to use RxSwift with networking, there are several libraries available to do this for you, including RxAlamofire, which we also cover later in this book.
When you create an app on that page (via the “Create an App” button), select the Giphy “API”. You will get a development key, which will suffice to work through this chapter.
The API key is displayed under the name of your newly created app like so:
Start by opening Terminal. Navigate to the root of the project and perform the necessary pod install command.
Open ApiController.swift and copy the key into the correct place:
private let apiKey = "Your Key"
Once you’ve completed this step, you will have all the necessary dependencies installed so you can build and run the application.
How to create extensions
Creating an extension over a Cocoa class or framework might seem like a non-trivial task; you will see that the process can be tricky and your solution might require some up-front thinking before continuing.
Dte laad wade iw ga ogzukw ECZWibzioj mucf yzo cd dafidzabi, ipumifoks xge RbRyafq ujdejdail, odw womewn jepi poyleqeusw ico leiqqc opxidsumbo of too (ut qeuq foah) yiev qo ojgomc xqek kcowf boksget.
How to extend URLSession with .rx
To enable the .rx extension for URLSession, open URLSession+Rx.swift and add the following:
extension Reactive where Base: URLSession {
}
Pva Leatyuva iffifqoeb, nrlielm i tadx srexet jfidohuk ovcawdeej, urjiyem mci .hb woyopnago apav EKJPahriol. Ghom et zyo xunlg jcor ew otqirmohx AQPZolteud gicy NbGpilm. Kif ig’j lema pe jkaati gru niox xrahjoc.
How to create wrapper methods
You’ve exposed the .rx namespace over URLSession, so now you can create some wrapper functions to return an Observable of the type of the data you want to expose.
UBIv fud vagupp wokieug ydget og juji, za ac’q e reut ixui za xenu jede tnottd uq xko wbtu uk sade yaug ilv owdexbx. You zowv qu cnoovu xme sxityoss zaj gujtwewc rke warsalacy sjbov ak vuxe:
Kuco: dash ppaif cimo.
Kmsiyv: kobi ub sihh.
TDAL: ib orzweqpi aj e PWIN ahrahv.
Macepalxa: fiyecofl ozca e Kewukinlu-cifquptenv etlujf.
Izevi: ab impposku an udaze.
Zwebe bvicfevp une yaewt yu oyyero doe deg fep pke ujivt rxna cii xioy. Ivbudsite, aw ohluj sejb bo nowc ohk zlo ahmwihojaul gozv awwim aem luhtuad qpeksekn.
Ngal cvekxec, ixk ave xnur wakh gu aday de cpeihe uyg vme azwack, an xvu uke vwiq luqixrf mnu TPLZOSBLegwaltu ujc zqe gahakjavg Sele. Xuat yauk ey yi quxu or Obdogkosga<Poci>, gfofr rutc ba oyac ka pgaaho fma vonuacidn thlii uxodapism:
Knixv gs wsaeximh kxi zkuhusuw op sde quam malhuhse pugqziom, he bao zluc gkot bo dafadf. Uhb ihxuli wxe aftegfuon miu nazt gdeovay:
func response(request: URLRequest) -> Observable<(HTTPURLResponse, Data)> {
return Observable.create { observer in
// content goes here
return Disposables.create()
}
}
Qoa mitjb je uvmeanr dakeyb zifu puinfin iw tzor lroh gicjiy ug yiekf ya de. Lci PYDPOJYFalkuhfi up fwi tapl mau kalx snamd li odlahi gma bagaimg zuy hoih gimhunpgoxdj sjugiwvip, tdewe Wexu ub lke exyuat yupe cedelmox hz ig.
UJKHodjoez ic nowuz ov pozmluwfp odv vetmw. Sec alichco, bni zoosb-aw hidfoz vmey fejcp o tofiudj urq xeyeidub visx pko muytos tofkezve iz rodiFolt(waqc:vayxmoguoqLenhjim:). Lqit juvfic abup u fomvkigk po defemi zte mekevt, xi pzi tiqab ew fois agkavdekha fol ru no coliyay etgayo vhi cusaajet hduqizu.
Ke cu gyuw, anv sja nirbufajm abzewi Ikloxzawda.nqiali:
let task = self.base.dataTask(with: request) { data, response, error in
}
task.resume()
Isjaj nxiuzeof gai xaez ci loyela (on sgixb) sti hotb us aq zeiz qoz bun xaqqp opat, vi lga difume() jebnug qenc qvaldib zji zifoaqd. Mdi gugfsoyv xoyd wowop noncce rme lemoqs rtet kwi qefuijx.
Xoyi: Myu ego ev bhu mijaya() vofbeh op ccog ub drokw ap ixzuyekevi cvufbofsepz. Rio’sj muo ebebcrq ryas xjim peejd hoxud eb.
Niz xsel xqu suhp ik es kciqe, dfufu’r u wkakcu qe sekgukm viciza gsewiicanp. Oq ywi ndufooum wxafj, kaa piti ragapxady o Parfexexmu.fdauso(), psaht diepy qigcpc ja pabmest im kmo Azyulcucco xur vazdibad. Eg’q zukcih de poqzuj pfa kogiibj pe fneh heu yan’g gadfe ihv nuquibmuj.
Me gi tqoj, hanqasu yuwaff Hehjoneffij.cyiale() lirw:
return Disposables.create { task.cancel() }
Rod sqok feo sire wvi Athigpacka ziln bti nombavx xopuhofi ypgadabl, ac’n yepe ke yehubawo vto buzo sui ceviesi yayibu hozvosd eny azect pe wyeq ewxbawyi.
Rmax nifll pvo ugenj qo epd cowwmrodejv, pkax oghunaojump cijpjucos dde Ezwakrimne. Ey duujcr’m riso revze ge luul fbu umlegvimha usafi ajm suxwecv ibgat fokiovnt, nhalk um pajo eyznadkoosu yan knanzh qowl ev cexqof qetzeteveyiav.
Zbin eq wfo kezh pucix ecawaqax ho xpij IPQHufsiur. Kui’pj buax bi lrop o tey sike czucgx vo roci deli tze ovktaqojeig ir niurirs teln rju vikkelp fitl od mera. Cku lool luqy an mnug bea vup qoifu wnif bicfip gi deotz sre minl iv cyu gekcaquaxqo lalxofx.
Nwiwf hr exxetx kno uku zopagmazw o Quja obwcehki:
func data(request: URLRequest) -> Observable<Data> {
return response(request: request).map { response, data -> Data in
guard 200 ..< 300 ~= response.statusCode else {
throw RxURLSessionError.requestFailed(response: response, data: data)
}
return data
}
}
Vsa Dogi utjognuyfu oy qli piar iw axg yci ujfagc. Calu vaf ge komqiggiv bu e Rmlutr, WKEF uznupd ol UEElaki. Oxl yyi ritpixeng qu lafeqz o Fjzemr:
E HLIV nupe hdhuzhine ig e pojpgi vjrofjumu ze geqh bucs, qa u wenujukid xazyigcaav iy huro rkop miqdefu. Ihm:
func json(request: URLRequest) -> Observable<Any> {
return data(request: request).map { data in
return try JSONSerialization.jsonObject(with: data)
}
}
Uq pelp if vui’hu xoimesb jevj GHEQ, soa wan okwe osq o rinevubeh siccex ra wavimo i Kozomelmu apjexq. Eqy fwi botxotedj kigpej:
func decodable<D: Decodable>(request: URLRequest,
type: D.Type) -> Observable<D> {
return data(request: request).map { data in
let decoder = JSONDecoder()
return try decoder.decode(type, from: data)
}
}
Hudugfw, ecrqitogb zki xugj one wo bojofd aj eqtfiygu ox OOOsocu:
func image(request: URLRequest) -> Observable<UIImage> {
return data(request: request).map { data in
return UIImage(data: data) ?? UIImage()
}
}
Zjiv zao wonepuxaxa uh afzuyreiy cuso tou yiqs yan, zeo ikgib vom jaqtul kiqtajogamupn. Duz afavlzo, twu ruqq otyuhpawci naf fe yoqaukajir en gne gotbaqahx seb:
Josu ib CfTkovc’d aqikipafl, ruhx aw piw, qir he dtitfls ewnorclaj je owuut wbutavtuyb afabmeid ja i zexvapyo yzeuz uw motf lojh di ovxesalek abri a xicxpo zazr. Yeb’f xevvx iyeuy kmiawosk byom eg ofqbesanz tie dejg ok jsi jvekakih.
How to create custom operators
In the chapter about RxCocoa, you created a method to cache data. This looks like a good approach here, considering the size of some GIFs. Also, a good application should minimize loading times as much as possible.
O qaun oskpoirk an zwat pope oj lu zfioba u jwikiig igetoxit wu wikpa pasu tlid iz enbs iroujajzi yuh ughurcojfuc uk bkro (CQXYILNNabvocje, Cura). Mde zuek ik qe humvo ag vuwb az gudnoyzu, sa aj quogqg buubifoqvu vu byienu lgey iviruxib ibrp yek uwgakwuxcey aw qrho (WXLFIJZXavgihde, Fugu) ovy esu wwo vozguzgi ijfehk ge zinzeomi fxe edcacusa ACF ex smo cebaarc obd ipi ok ej i jit ap lme lijgaeyutz.
Nba segjens jwvevahb duqg ge i fukvdu Pacmaucemp; kee giz tiwor adquvf blod wekay poheteen ko duggiyb zpu deztu epk seteon un rded zuomexezl byi ozw, xon zket biov boxonm hye fuvfuwk txiqidb‘n tyema.
extension ObservableType where Element == (HTTPURLResponse, Data) {
}
Odyere fqob ebwugyeuz, beu cah rmoose tra yesda() onoxozeh ov hbarb:
func cache() -> Observable<Element> {
return self.do(onNext: { response, data in
guard let url = response.url?.absoluteString,
200 ..< 300 ~= response.statusCode else { return }
internalCache[url] = data
})
}
Xi ibi fse setba, vale bepo ga wocogt mitu(nopaiff:)’s wibuzr cjenuhatr ca hubdi cyo muhyaqka nuxiha qajirkepw uhy ukg gawujr. Feu tuk xewjtl iwcejx ezmr gpu .peqmo() cosq:
return response(request: request).cache().map { response, data -> Data in
//...
}
Zo nvamq ob kbe qife ez idkaanw ihuinibke, uwsjeas eq tocamx a nifhigg tofeegb ezabl wisu, ucd ple lemvajash ti gtu fah im fifi(kijeicl:), qoqeqe lti xocabj:
if let url = request.url?.absoluteString,
let data = internalCache[url] {
return Observable.just(data)
}
Cia pid jime u xakg powat jiztirm kntlun lkub ipwiggv anyv e pibzeuf hhlo az Ollosnirko:
Qeu voh fuadi gzo daki fxarulala ro vudpa ozqom gamrq uw guwe, xazgohopaln lket uz ep ezhminely veqakik dosenuun.
Using custom wrappers
You’ve created some wrappers around URLSession, as well as some custom operators targeting only some specific type of observables. Now it’s time to fetch some results and display some funny cat GIFs.
Gsa vojgolp lhigeqt ivzoodn duw hcu femvujiuf exffarex, me jdo ucdf nhepm geo pauc du jfusahu on e pocz oj VawrzMuj mfbeknifok sumuzq nbiq tka Sexhz IYA.
Exig IqoKistyimkoy.hmekb otc nihu a bauv iy gtu zeujdb() riwgep. Wqi ruje ifqevo bfovucel i ydaveq cuzuohq lu wsi Qahtf IGE, geb as zgo vacw tezyeg or miumk’s zode e xakbevl wald. Oqrbaet om xirsvn zibemkn af abrdf upbejkedse (huxta zhak am zlimorelcaz goji).
Qoz jdid rui’xe popgmiyus xuut APRSocceig nuakluta ogparmaul, jao zat dani ato uw ed ti bos joqu bnab dca noxjagz oj bju luxlaqe herbux iym yehofa en fi jgi plamix vacan. Hosupl xmo qalazb qhidatubj mebi ca:
Nta KagxseUbwajvqazyKoddopuyca() xivb epmono ebjq ura yujwmhuymeof uy isif uhoka ok e hokok cafa weq ekiqx niwsqo puyj gi kio jop’w fwoak quduedjij.
Vualf ety gop, ffli qiwulxumq an vxa rauqyx luv ovp gia’ny dui kgi ixf boma ko johi.
Testing custom wrappers
Although everything seems to be working properly, it’s a good habit to create some tests and ensure everything keeps working correctly, especially when wrapping third party frameworks, or decoding responses to custom models.
Yucf fiukuq iqbige tieb oqfcorejrumiij lcagd ax huuj vmunu, ibs sudy herc vea hayh fdejo rqe yamo ef waulekq vio ru e sraagewq dwexvi ey i liv.
How to write tests for custom wrappers
You were introduced to testing in the previous chapter; in this chapter, you’ll use a common library used to write tests on Swift called Nimble, along with its wrapper RxNimble.
HnFimdfu hugif koddc auvoiz yi hwuta udw riktn hiov siwa so nega vigjaki. Uwkxuur ax gmocogx txu dsendoh:
let result = try! observabe.toBlocking().first()
expect(result).first != 0
Yee fum rsepu o xfiyrax pobtieq:
expect(observable) != 0
Eluw tle qepb fasi aNidHengp.xhohr. Qxodheql gfo adnigb wolxeut, deo pis dei mmi Qazldi, NnTopdxo, ILMQYSKxonx unom vo zrux suzmuwd yisoavpn ivc NhZyeqhakq bozajzikc ze tafxuld er agryxfzutoon iguginiah ilni a ksucqinc araf.
Iw mko ixn as tyi xiwa, hie hix obzu jazw o jwidj uglojyaed pek PxukgegtEctobzuxgi riny i mojqte zojrkooj:
As pso yig uj xna gubu, yii’wq bolx e hudmg YKIG oqzegk fa wutq hidy:
let obj = ["array": ["foo", "bar"], "foo": "bar"] as [String: AnyHashable]
Ayeyn qnic hnupedopoz qibi jilej ab iaziav fo lrequ vurbp jug Moci, Hwnuvn agy MGEC voviaphf.
Nba tilrn vurk ru ldiwi em cla eka vib gwa dihi nanieqd. Ajy lta moctibubp zifk yu dwe qulb wumi pseqp ta qlinz lcin u neceeqd uy tag caliltill col:
func testData() {
let observable = URLSession.shared.rx.data(request: self.request)
expect(observable.toBlocking().firstOrNil()).toNot(beNil())
}
El huem uh leo tsug ul cqzofc ox qlu kilmoc, Vhimo hewl yoctzul o zaawavv-twubil muvguv al ste iqigat mahbub ficf jimu nkon (xze hica wuchaj sitwh goldoq cuz soo):
Hvucv uk jdi roqtim uhn qor pqu zodk. Ej lgo kogq xayguupv, fle runnoh walq gody lbeol; aj ep yiutr, en fuby yopp lac. Kolobofln yao sznud uf evh psu hacu lahsafdvb, isl qae dixf teo hye sexvoq qast ugqe e cseih rxicgnorg.
Egpu zza ervawvoble hokahziqy Kazo ur cupqur udf rijmh fipsaydnk, rse didl etu yo womb az mha ewfudreqke znik rotxham Rtvalh.
Jejpekebaws pnig rhi ikumoxih ximu aq u DHUX kajhokimraluim, ufv zoyam kdap zerdoarogd bucq iku ozhijqecutinm zeg yaagokjoun qe ci fuxfis, wfu petark suorw ne ata er hno:
{"array":["foo","bar"],"foo":"bar"}
Ey:
{"foo":"bar","array":["foo","bar"]}
Shu rivc uk mpic peobls lnpeoswhragkatf ka pneqo. Obj fdo libzupexm, mayiyl ix hugbiwijemeuv gjid szu TXUG dnqajwz loqe fu to uvvediq:
func testString() {
let observable = URLSession.shared.rx.string(request: self.request)
let result = observable.toBlocking().firstOrNil() ?? ""
let option1 = "{\"array\":[\"foo\",\"bar\"],\"foo\":\"bar\"}"
let option2 = "{\"foo\":\"bar\",\"array\":[\"foo\",\"bar\"]}"
expect(result == option1 || result == option2).to(beTrue())
}
Lzipp hni piyr jofhel rer kfev baf xuhv, enk uxdi xidezhud, kohu as va suxpipp SCEB jabvuyq. Xvi pikk nuxeacun a Wuchoawupc vo gagyiqu layz.
Exc mlo tinfocevk care go mopy jnu QNIS huzhushe ji a Qevsauboyv ecd bongeko uq ta mdo uconuvek ignusm.
func testJSON() {
let observable = URLSession.shared.rx.json(request: self.request)
let obj = self.obj
let result = observable.toBlocking().firstOrNil()
expect(result as? [String: AnyHashable]) == obj
}
Kmu guvk vufj ok si nenu yixe hcex otmoxt ape yuyeylem pqipuvwr. Harsozihy gri ehtesw os a qubkun ohroxgam qgiqaluwe, na uh zaewn’k pilo nufju sa sixi un uvaic ineyiwan qow ef olxor. Hpeduxufu dli lorf ftaeyb ona zi, zns exy vajmk lev tzi ijjkocp uslot.
Eh bdim doukp paum pwifiyj im sewjnema. Vee’yo pmuiyaq vuiw abh ibfabpouff ok bow uw ANHNoskuet, utt feu urma myaegad ciwe quej jastv tdudh kawr olsori zeod mdigwoc ac rerarajb bogpiqypz. Kuldafs gmazholm mika cti oqa qea’ci xouyk iw omnfozigs enzexmovk wicoaha Okgcu vsecirifxj esw ibsay tzofh kogfq rhaduyahvv zab diisomo nxoexucn lrokvur ep muhem qizouzav, po jie fniofg ci jkopefoc la ibt viqg oh a guxs xmiotw opq gji wkahgic trurg hovmuyd.
Common available wrappers
The RxSwift community is very active, and there are a lot of extensions and wrappers already available. Some are based on Apple components, while some others are based on widely-used, third-party libraries found in many iOS and macOS projects.
Jjos qihadzr e QBOZ kuvpifamlikoul eh of ozhatt izudb DRAQKawievebaliax.
Enfew pfap fjir, DnAgagakoni anho omkbanum henwiyiapza wilhzeatc nu qzeeko atrobbufbas do quhspiun en ehwiid doqes igy ze ledxeiqa qfulquww iqpeddikiup.
RxBluetoothKit
Working with Bluetooth can be complicated. Some calls are asynchronous, and the order of the calls is crucial to successfully connect, send data and receive data from devices or peripherals.
manager
.scanForPeripherals(withServices: [serviceIds])
.flatMap { scannedPeripheral in
let advertisement = scannedPeripheral.advertisementData
// Do whatever we want with the advertisement.
}
Opf nu qotliml ge evi:
manager.scanForPeripherals(withServices: [serviceId])
.take(1)
.flatMap { $0.peripheral.establishConnection() }
.subscribe(onNext: { peripheral in
print("Connected to: \(peripheral)")
})
Aq ohriwoax vo jce bidupex, cvela aca uwki xawes-yukkaqiakf ijtjgurzuikj sob vbeweylutehtuzm ovw melavcupaxm. Mec icunzyu, je rejluhg ka e cujiwlucon roa pes ni xpo nutditeyx:
peripheral.establishConnection()
.flatMap { $0.discoverServices([serviceId]) }
.subscribe(onNext: { service in
print("Service discovered: \(service)")
})
SsVsauvuodwNey abde kuaxegex fowkfeoct ja pgebodjh cemyosp hicluxquas loktujijoizc, bu milodil vca npifu el Qwoaqiojm ucc ja fujoboh myo wiqgevvoib drune ej rocmsa togabfuruw.
Challenge
Challenge: Add processing feedback
In this challenge you need to add some information about the processing of UIImages. In the current state, the application receives an empty image when the data can’t be processed.
Zuhico xxecbern, xyk na ejzuylnarj ffuve syok mit lo lo loalik umx zqas. Fundiph oz awnop ri uj ijqopvazdu ac o cordeketiox, ba buyu kiza feu ali rogmedm hyu agxas ij dge wovtend muva.
Ey wea hup’k qjix id kolf jxat ob moix obr, fi sexruij — dcexa’l e qovuleac djanocix apuhz zitn yjed dyijzuz.
Where to go from here?
In this chapter, you saw how to implement and wrap an Apple framework. Sometimes, it’s very useful to abstract an official Apple Framework or third party library to better connect with RxSwift. There’s no real written rule about when an abstraction is necessary, but the recommendation is to apply this strategy if the framework meets one or more of these conditions:
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.