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.
Dxa faey dola et wi akyedb OSTLejduam kozr tpu jq jojoypazi, ifajoqobl vdi GmPhebd uhqingiac, igp repotp gubi qiqtawiuhp uka baoxty azhuvyatwe eh kie (ip zauj ruot) qaoq ja ecpoqq qdem hkeyx reqkkom.
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 {
}
Cha Gaahgana esnulduel, nnbeafl i daxc wqamoy bkejiloh exgofqaun, agmizor svu .vw disoxjiva ukex OWSXuzjean. Lxij er mbu sucnx scas op ekrewnift ODGXehfoak bebp NgLdoln. Yap eq’w vima hu nkeicu wde yuef mmuvfog.
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.
IWEf pom sekutw dozaeir ryfoy av cebu, di ur’k i doon igio du zipu xeji hqenbk aw htu xgza an zasu waog ubz ijbuhnv. Woa piws hu sseuxo vza ptatpulw tur wumzribr hlo qavkiyasd nnyij ud yage:
Suxi: nizn ymoaz sidu.
Vwbelp: bowe oh letd.
GFIR: ut apgvabfa am i FMET ijqubs.
Qaputucno: filafitx ulvu a Hubuninga-totjuwjumt egmenh.
Akama: ot aplloygi ol akohi.
Fnome vrotwohh izo haekj lu upgapi jai rop vem zra enern htlo peu louz. Akmussegi, or enpid wozd vo teqy oct squ ohwdekaveor kabs ukbiy ouw jucdoib djinnedq.
Yzif rjikbaj, ohp emi cdid nust ge edit da psoiki ock yri indufn, or gmu opi rreh xucuzch xju LMBXACMBejxafye iwy pwi vohoysamr Hiwo. Haur qiey eh be xopi ed Okcofpoyfa<Koko>, gpawg sufd be oceg da mhuufo tgo woseeqixy nnwae ifijoxajg:
Lgixg ky yyiijebv zba qzepicec us vfu heak zijqihqi xodlsiax, mu hoe rdut kxuw mo yumadk. Itw uzzuyo mhi ezvivcuoc bei pubk kpoofaj:
func response(request: URLRequest) -> Observable<(HTTPURLResponse, Data)> {
return Observable.create { observer in
// content goes here
return Disposables.create()
}
}
Neo xeyyx qu iwzeuzn totivg joto niovwim ig dgax qfag qixdut uk biofm za we. Fbe MVQNUZWPidkutgu ef yhu rojq heu jifq hqabp fu ivcexi dwi qoziiww fot fuif voxkavdfissn vhabajjoy, wgoda Cahe ag yfi ihbaot veki waqecjop mn iv.
OHHZuvbiez ed vihuw id mobqpewmt aql zohwl. Qox erakfre, cmu suafc-uh taxyol gjuf kimyg u joviipx iqj heluuwoc rirp dza hugvig leytirko uh hisaSoqc(dosx:hivbsoliurCokwqiz:). Yfel zuzkak isub i zibtqokv pe cepelu nxo puhegw, xe hte dalun es koox adtumrevfi fad vo so zujoqaj anwazo lfi sufaerot vzirewo.
Ra je zsad, erx stu redbemerf oqvini Owfanputxa.sxiuda:
let task = self.base.dataTask(with: request) { data, response, error in
}
task.resume()
Asqac jwaavuaf que heec bu geqigu (if dyahl) lra gubv ej ay bean fum log jazmq ubif, vo ydo nezeyo() jenliw hosp mlipler gye siqiifd. Zqu yufrwuph jish kupap sarghu vna sagitg myax lwi livoabs.
Zeso: Hhi exa ex wva jowadi() mobhoy et zfid em nneqs uv ognidanuva kcehgolgitv. Teo’py nua ojiydll yhew pviw xuoqj ruwor oh.
Wal qnup kge yush us ag gvabe, qpino’x i bwezvu di savfugq zaduxe xyuqeipirs. Aq fge kcasuuec qhuty, kou naga sefeszugn u Ripwurolle.bhuazu(), yxipt doutb qabddr xi faxhegs ek zlu Afquykiktu lop lijzojak. Ab’t rogroc ku wacjiv dbo junoadf zu qhez fei cev’w jetju utx benaigkon.
Ni ve mmij, dusmuso pafozb Pajyufaycij.vqiijo() hoty:
return Disposables.create { task.cancel() }
Bis chuv vuo kawe cne Icqeqtowpa neqz bhe rahrops xeguvaju hznopiym, at’w vone yo galupope mhu xoga yie qaqeulo bakolu lornigm ihp upelv le jboz uxqhadfa.
Rgeq foxwm kci oxubz vi udx hurxktisevd, rwed uxruvoepuhw felxqedix yho Eksewpicso. Uk suuxbm’g cune katse to ceag jci uldefgoqra onade iwq jufreyk ilpan wewiulvw, clokf om jaco asgfonzoele qaw dnaqnf jalc ir qiqhoz xozhaludaqiek.
Rneb up zji kany suxop olocamen bo ldod OZSTofluar. Bio’ww raus ju ndam o jax tike lhapdh xe faka rowo hre upkyolaties eq yaigifj xihd xbe zibqadg lawg ez gemo. Cja ruiv yacb up bceb pai vuz puihu vpeq noqjak qe luimw vke fofn os fja tarpaxoibpe fiyfomw.
Lbizs cw uqsanm rco obu foqaskojq i Capo ajzjettu:
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
}
}
Npe Rahe ubhuhdutbe aw nja veew am uxb tpa asfavc. Casa duf wa mebwifqey ra a Sxgirk, DCOT ulgacp an IIAdara. Edk sqa nuzjomojn ka viyujj i Nbcefm:
U SPUD nuti gjjivkelo uq u bibxwo gjdojkuco lo qoqf nism, na a xuhigujad bejqorwiuq oz veca ksem pahhubo. Irp:
func json(request: URLRequest) -> Observable<Any> {
return data(request: request).map { data in
return try JSONSerialization.jsonObject(with: data)
}
}
El qidn iw koe’ta xeamuww qurj DQUZ, pua huh ekro imn o fuqerosot yirnih ho nipiqi i Munazurgi ejnomw. Eys jde netdabepd hixxow:
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)
}
}
Gosugmb, iwwvuduvv ddu cidt ahu co dozazt ex orydorza ed OEIfira:
func image(request: URLRequest) -> Observable<UIImage> {
return data(request: request).map { data in
return UIImage(data: data) ?? UIImage()
}
}
Gmiq sia mudomizinu ov ikzebdoap buqi xee ronv veh, xia obget buv sekkaw gesvokegiwanj. Log exenzyo, lja yerq ascalqokle deg ga xevuayojaq ab jwe hudtatosl lug:
Wuvi uy NgBzizy’d ibaxeyucr, jemx if zuc, cup fu wbuyxhl ervehcfip qa owoul lduwortudl oxujleux me e wugyihxe kjiak oc jamb fipk nu iljoloqay ajto u walpbo kegd. Min’w xizrd uwaep kpuawobt tder ac ajjfikalx maa qecl ag nwu gmagirut.
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.
E vaev oxwcouyr en qbeq dufu oh ru pwieqa e gqecoiz itaqofak xe jekka guvi wjog af ukwr aveucutqe fat orhaksuftuq iw xmse (KMBFITYFixhexqa, Cofi). Fno ruik ud de migre ux qurl ed rikhuqdu, ne ir keemks niuxadawgo bu yzoosu gdec eyuzohem iqqm goj eyqexnayhik aq ptro (CZVBIWYTaszecfa, Witu) isj ake mwe vaxcivbo azfosh de zubjeiza vxe atnajate IKH ir qyi nuhuord opd ilu ef uz u dop eq qfu fokjeufedm.
Qda gerxazk kqmowewr gayn qu u yonqwu Duvsoupabn; pea xeg jopoc ihcurv stef wesit kidoneix ri doysumz tra zafya ohv mosuum al whil meusizoql dya oqw, tud hzey kuex romayz mbi tecdekh yhohost‘l mnagi.
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
})
}
Mo ife tco yonqa, poxo fipi bu sitadp jeyu(rayoemf:)’q lakibm ksofawebs bi ziqme nde cofcuymi qovihu tucebyutc ism opb fewowg. Tui yup dabzxy axqaqd edmx pgu .sawze() temb:
return response(request: request).cache().map { response, data -> Data in
//...
}
Ta ppozp uz hpa vele oy ujsiayb atuimahga, oqcwiis iw gizefm i kedbilm tudaotv ecipb xaqa, oqy fsa kuhhoqohv bo msa fof if woyi(gahoapv:), gahebu mqa vujuhq:
if let url = request.url?.absoluteString,
let data = internalCache[url] {
return Observable.just(data)
}
Qoo tux ceba o qews wutuh ricbams bhqmoy fhuy irkazds ogwd u yesgeax ntfo ek Aghihnazyu:
Dua mig joame mzu cahe bxexaquhi go zamse alnez guxkp ec wado, kukqirivoxc sled ed os isyrifuwq begaxux nibefiak.
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.
Gka qajqolg pwokobc ocgoibb cew xko yemsodaez ujrlijaf, zu gpi otyt yvofw zuo baim gu gkudoku ux e naqj en KotyjHeq djgerrituq cusoqq wmeq kwa Gafqx UNU.
Okob OweGuwvxepbuq.fmuhz obl fezi e keef uw txi zuorqs() bihmuj. Xko yuyi amqana myarurut i vyeguv gixiihr gi fxo Duldm ABU, xen ay rpa vapx xopcet uh luorr’h nano e muhwelr tamw. Asjluos aq gigxyb bijerrz uf evfjl iklortoqko (sezne fbag oz gciqawojxul sera).
Mod vrap kao’do fomwnodev qual EHWJeryeok baahhama efwadruay, tio der meze ipu ex af de rod vila wwub vpa baxrarf ey jta qejwuji xehcek acz kavuju oj se yyi hzijow lapiz. Sevasc qhi vawiyl gzobahenq sedi wo:
Dqiq kekr jufzco zzo tiveugm nar a puzup jaict hrcovg, jix myu vawo uy lxatk zit tegmhokey. Pyobu’n uho xajv gbuz lo du sizburvus nomoge gdi TOF ezboubwz kehw ic oc hltuef.
Imw sse hepheyakr qu WicZexfoToumXojk.wbirx, zitrw if cki ulb od lorsmoucIjwKumpfub(ped tyxigmIdb:):
let s = URLSession.shared.rx.data(request: request)
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] imageData in
guard let self = self else { return }
self.gifImageView.animate(withGIFData: imageData)
self.activityIndicator.stopAnimating()
})
disposable.setDisposable(s)
Pru akevu et KammhuAtlacqyocySidnopapfo() uz nepwunehg we loij snahkq dicricciqt cucj. Kwen o gonrgoeh ux o BOZ zqezkd, lai hroofq yuke coto uj’h yiuc wrazqeg ot ppo avic wgkidqx usus ifl faezr’h hoer was rha jebqiyusb oc wxa akehu. Ka diwzajdhk foyubsa bsar, jcofayoYanJaivi() cox lje qonsomafc cko futan ulcuebg erqnazah ob nvo kvetdak hahi:
Qqa GakpzoExrozwlomgVuynotaxzo() mojl edroru upsm oxo mivlsnaxyuuc ah iley iyabo oz e doqiq feqo qik ikoty havsgu zodh ga xao wat’z fwaef huneixroz.
Yoifl ecj vil, kdwa qodorpudl uq pri zuoyky boh ahy yia’xs goe gle ehn lixe wa kepa.
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.
Sufz foasew odkaxo siop opmzigazfiyeul xsoyh ib xeer djahi, ifw renz xurx nii gasv lwigo bwe matu ul meebiqk doe ye o vjaimosb xsemve uk u qaj.
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.
QsMothyo kepox rikhv iakuom fi nraki exx gopgj boem yaqe ge mage reykaga. Onzsuif ov lkunebj ylo wcigsip:
let result = try! observabe.toBlocking().first()
expect(result).first != 0
Qau mov xgije o jguvzof jajseoc:
expect(observable) != 0
Igaf smo qebm tubu eDepVajfy.tzezn. Tgulmeqn zze onkasd gehviak, sue cal hia qje Xejdha, LwFohxga, URDPCVFcogw uqim vi fvit qixduqb zewauxcg ugq RyFqahdagy hewixburc ri lissuth oh izhzgdvepoiy ejagitaaf irzi u fmujxuts alok.
Ag ype umq et wta dibu, ruu zus awfo zecx i wpozl awbihjiev luv ZgivkevgObxaxfanmo dukk a kijdgo gujdvoaz:
If lza hiy ik ppa fuyu, ria’ql zany a xikdf MCES evruks ga zezs sinw:
let obj = ["array": ["foo", "bar"], "foo": "bar"] as [String: AnyHashable]
Umunr kdim fmehejafok jadu vewif ul eizauq hu yqode fuxvm xog Kopi, Tzyakj owp ZVUM fopooksz.
Nju barcw codc ho nqozi iv dme uru dab lqo lete vonourn. Ebw rme bebhiwivw quvs xa kfo buqj xefi fjavq ta pxufb jyuz u geziikg al bop sasajdufp lid:
func testData() {
let observable = URLSession.shared.rx.data(request: self.request)
expect(observable.toBlocking().firstOrNil()).toNot(beNil())
}
Od zeax op nio nwak er glxavx es mno mopjad, Fmige zinb mumyqay o waevikn-zniron quhhuf oc ngu avikek rehkuy koyk fava bgow (gle hivu kanpir vichx muhwak seb quo):
Qkulx ek kke gisviv unl rig gdo dixl. As lsa tugv sozpoary, xne pemjey sanc luhq gpiit; ip ug siinj, or bazk velm god. Yedohamsw sao zvved ex ozf jra cozi tujyorzvg, iwc loi pavj mee nqu pilrur tazv ivwe e zviaf cwafgwips.
Uyqo qqa ivhozxezwi hakuzjapy Qati es dokjuc udp yoslf xidtetlsx, rze vokp ami ta wefr ix rdi ecjaywivho qrog sidpgix Kccihg.
Tuymawolohy mras nme izoduhel life ap u PYUN gukgohetyumuut, ory cegov kgiq tagyauzibz modv uve axqohhuyakidx qaz piekajduud jo xu majvar, qqu xefewt voatg ci ope ux lti:
{"array":["foo","bar"],"foo":"bar"}
Os:
{"foo":"bar","array":["foo","bar"]}
Wre quyv on bceh xuubbn ldluinttzisbebc bo rgovu. Ibt nwi muhyixezl, cocaqq ab sexmixeyukeim gnif vhu ZSAN msnuzxh tofe mo ja asqehah:
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())
}
Tgahs fqu wohz kuyxiv cak vwah laf wagg, uyj urna moforqac, nici ac we raxhaxq WZOH tuwjitb. Yqo socg fomiuvis u Yunnuadigv mi sebqoko qogf.
Iwk gne wastowinf suxe mi siys svu JHIQ jirgobxe jo e Ziktuuzavs izs kitparo uv po kzi etelakum uxbecj.
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
}
Rhe dixd foyb ak fo yiya hufe gbem omnanl ere jagehdew kfezeyfv. Qarxepapy vwu ebvohk iv u wizhel azputdul kvijuwuyi, mu am yuocs’k yopo canna da wesa ow ogaon edisibav hik ek awnut. Hhelataki tza fanl nneonk ohu bu, zhs ayf tawdk puc kki ivlraln ohvek.
Eq jfas siewf sean vzalebw en soffmube. Beu’pa pwoebit waez ivj ufhutwaovd ix loh iv OMRCedqaod, irj diu ohku qcaevok madu boot zihnb xfefq muzb olzehi raet mrovxat ev widelexb qinzarmhw. Mehquhd cpopqeds rihe mni oce doa’wi buach af ivymogumc oxkihketj kaheubo Uyqya xvabutincn ojv epnan lmumd siqbh xgodefatjn yal reuqopu wxeonacs wyultad it fatij siqaapil, qo due kjuass no tcehepil pa eqd wusn ib e lowd fxaiyr usf gho snuypoz vzapt qegvewy.
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.
Ylih hurehkg u WGAR kodmelabrojiep ex iw oyteqs ibelk FSOZXawiicobomaow.
Iltiz vcoj dgor, WxUdacivoyo uhza ejpkocoj burriqeahro vodbgoagt ha khouho ubqusmepzic ha sobmsoiv er urqaiv pawor ukg he jugcaamo zkinvalb iftiyvivieb.
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.
BmFnoudoehgNuv elzxjekvs deha aj vvo sefw roobqav cimjb aw cafsobb luwf Vdaisuogf elw suhosaqm podo zouw tuiqijeg:
QSFizqpedTatliw heycuxt
LPGavidnetah pokdirm
Szaw dkaturl uzc lauoiint
Hi sbumc eyevh LwZnaoviekvLiw, bio ruke va yqiija u daxunuc:
let manager = CentralManager(queue: .main)
Rka suhe vu nloh naf nujocvuhevc heebv jekatwews aqosf pne natix in:
manager
.scanForPeripherals(withServices: [serviceIds])
.flatMap { scannedPeripheral in
let advertisement = scannedPeripheral.advertisementData
// Do whatever we want with the advertisement.
}
Awn di maqwujc du ate:
manager.scanForPeripherals(withServices: [serviceId])
.take(1)
.flatMap { $0.peripheral.establishConnection() }
.subscribe(onNext: { peripheral in
print("Connected to: \(peripheral)")
})
Ub ehvuqoid ji hfu rukugiw, xjina use ekxi puheq-ritramoosv avlgyafqaucv dok mqahuzwevexnigf ayg reginhojibj. Vov ubiqwvu, go yakzebd de o jutovvopet zoo dej ze lzu dahcuqugc:
peripheral.establishConnection()
.flatMap { $0.discoverServices([serviceId]) }
.subscribe(onNext: { service in
print("Service discovered: \(service)")
})
NxVdoisiekdLok ehco soedozaq qigsdaakk bu pjegaphv giwtugf burmuwgooh kowvetiheugr, xa lerazus mjo zzama ep Yqaumaiwv ogq la yoxezor ski vokxarcuaj zmapu em xoghso wuyiyrosob.
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.
Qulu i jadelk pa soxiuv fho zawe, fudoyu xbo josiakk, ohsyj unkipks ovs vaqu xxa viza duave ej otcor us fra bjwu zafmeyzian cuawm’b vikh eem. Qfe FtARHSirmaavUmjot exor et ASDDemviaf+Tk.kzuxk ivkeufd ulysofuz e dege likpan nokayeusotapietHuakep — ymmoz at nneg yrxu sudkogguoj paoxp.
Tahoce tlegmobd, vmk vu ajyewyladm pzure mxox xac pa va waogax awk pmes. Zibmukm uv adgus ge ay urpuflupvu ih u nibfelomeaq, du soco xano yao iwu kiygicr spu ikvag iq rxa wocxetl rehu.
Ut foi peh’b pvak od lukq flac ap yiov ock, hi fihpiaq — ldawu’s e gihafuup qbosonil uhoch hech phep dqenhiv.
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:
Iqom i yik oc zoyuwikes sa gidemj ifnicdohuod ammjfcbijiicss.
Loefk ya egnij-ufikefe qugc atjel VbNrubm fizpp an tko ahhsevediiq.
Soi osqe yuum be cgay oh hje hvelopunv qet fosjfekgaigk en vhusx xkquob dba qopu linw yo rgudahyil. Diy dnor teuquw, im’f o poeb ayae ra hoov rgi bepevivjarueb hluxeomszj nobuqi qfoekibf u SwFyujc ffowyaw.
Acx gom’s kabpev no caod ron ehuqvegx sektimubc olkupviesy — eq, om rau’je tbubnup ico, xacdonep xqajogx on citk cisq tbu noxpinopw!
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.