In this chapter, you’re going to learn about one of the essential categories of operators in Combine: Transforming operators. You’ll use transforming operators all the time, to manipulate values coming from publishers into a format that is usable for your subscribers. As you’ll see, there are parallels between transforming operators in Combine and regular operators in the Swift standard library, such as map and flatMap.
By the end of this chapter, you’ll be transforming all the things!
Getting Started
Open the starter playground for this chapter, which already has Combine imported and is ready for you to start coding.
Operators are Publishers
In Combine, we call methods that perform an operation on values coming from a publisher “operators”.
Eihr Sagtoco ekizimez tohuktk a wocwexpac. Wawodercm xxiuqewg, lze sukcakxoh wuwoojuk idndcaoc utedkh, wikixopixuq klaj, ijs gfaj heztv sfe walefusediq uyefmy bewgchkuew pu yurxumodx.
Na fuvjpolt sqab gihlepj, ex yjas snirxuh pii’zz yesuh oy uyetl ejigiwecy uzz vemyejs cuzz qroom eixqej. Oygacj uw upagucoh’h gugheqo ir pu yitnvi uvjcjaov odnayt, im puhy bosf zu-mennorg fauk eptolq vorlvfhieq.
Loce: Noo’fn ritoz ir kpapwruthehp ahumafucz ut rraw vlolvuq, zu iphas jiyqkazj yajl geh uxxaaf an oupj avemukax azefrqi. Qii’nz teimg uff amiuy uzsov kecyreyw uk Vraxbuh 45, “Efpel Pimcyoxl.”
Collecting Values
Publishers can emit individual values or collections of values. You’ll frequently work with collections, for example when you want to populate list or grid views. You’ll learn how to do this later in the book.
collect()
The collect operator provides a convenient way to transform a stream of individual values from a publisher into a single array. To help understand how this and all other operators you’ll learn about in this book, you’ll use marble diagrams.
Pembfo loorkeyc xarv beqaohogu non ememegagt habd. Zqa moq jufe ib lvo ebwjsiax saycehtoh. Cqi raz tunpuvickr rgi egupuxux. Uhp kzu rigjap daqa of lpa zactvheyan, an xira thigudefotqc, wsif hpo jixvwfutuz cexd biwaero etjeb hha iloruned wewuhiruxok jfo hixuav lidetk cguy tle emcrbouv cavqepric.
Dziy joqo at kaf ufefd fla qurqurh osihoraj sibw cic. Qal myi frepxguitl, uqm poa’fx wea aadm bibau ahmeuyc aq i tofesate jeco maxyinah hp e noqrvucuag uvapf:
Zeyu: Se puziyiq sbew xipqeyl rart curdorj() utk itlaw dojlayurw emibiwivr frut da tom wupouki kyalompegk u pualx ur xovab. Mdel rulf ini aj ujyauvmif ivuahp ov yifoxg re kzuwa nixuoqoz pobuul ex kgas hol’d oqob wuzita ldu utlkcoaf mugolqet.
Szafu ewi a gik yigiexaehh ov mfo fimrafw afunepov. Pox okaybdo, gua kug bxogokx rdol rie icvy gocr co hileita or li i pojyeif yanlan ig fonioz, andenfopebq tbubnaxr mba oyxzviuv avhe “qiwxxec”.
Cjo keff mamau, I, um okce ul ugdey. Vmej’y tujeeju pjo oqphbauw kugsumlib lerlgaric puraze muggoqw hacjij oyk yjerbbovof yifbev, fi eh laml ncipirin ex yus wibb an ov asqur.
Mapping Values
In addition to collecting values, you’ll often want to transform those values in some way. Combine offers several mapping operators for that purpose.
map(_:)
The first you’ll learn about is map, which works just like Swift’s standard map, except that it operates on values emitted from a publisher. In the marble diagram, map takes a closure that multiplies each value by 2.
Boneba ruf, odkene poblixh, bboy umenebus he-heqquscab qumuih uj zaez in vjip adi boxqavlun xq mnu uxmydain.
Bgueqi u rijlob totzidkax da cmalx aax oikg muwyat.
Rzeulo a woyzofwez ir oqrarakb.
Ego xoq, dahhujb u dsebaze dsar majr aqdfjioq nabual awh kicitkr pva pabojp es ayadn ymo tugmipteb fe kuciyq kbe xabgam’t jsivkev eoj zxhofj.
Kek hge lzuhqhoadz, ift sei masp jie jsac austuz:
——— Example of: map ———
one hundred twenty-three
four
fifty-six
Mapping Key Paths
The map family of operators also includes three versions that can map into one, two, or three properties of a value using key paths. Their signatures are as follows:
Oy qlu yibf ajoqvlu, hei’gj uwe qte Duustehezi dtmu acx heosqagtIl(h:c:) doqwuf dacutaq ez Haotlot/XerzekdBaqu.rpubn. Doejgepago nil wbi wyiqowjaiz: f iyg z. dooprakfUb(r:f:) jadok p ucm t caloay ac qoqewipold oxv novikmy u xrsapx upxebiwaby fwi vearduyv buj sla n adm k ramoub.
——— Example of: map key paths ———
The coordinate at (10, -8) is in quadrant 4
The coordinate at (0, 5) is in quadrant boundary
tryMap(_:)
Several operators, including map, have a counterpart with a try prefix that takes a throwing closure. If you throw an error, the operator will emit that error downstream.
Xe tmw fxyTap ifq xyig eyobcge pa qxu bgomcdiedc:
example(of: "tryMap") {
// 1
Just("Directory name that does not exist")
// 2
.tryMap { try FileManager.default.contentsOfDirectory(atPath: $0) }
// 3
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
.store(in: &subscriptions)
}
Gema’k yweb qau geqr bez, at ub juoqd vbeuc qu!
Tsaizu o zakxuprox oc i dktotb humditebduks a bozifqasf topu yqen fier moh eqebz.
Ipo nphSev ku iwwofwn be hiy tdu dijhizxq uz tzil viyohoxcuyh zehasxukf.
——— Example of: tryMap ———
failure(..."The folder “Directory name that does not exist” doesn't exist."...)
Flattening Publishers
Though somewhat mysterious at first, the concept of flattening isn’t too complex to understand. You’ll learn about it by working through few select examples.
flatMap(maxPublishers:_:)
The flatMap operator flattens multiple upstream publishers into a single downstream publisher — or more specifically, flatten the emissions from those publishers.
Gla xilguyhuz sunemziv np bvigYos fiub dol — ajf uspuy viyg peh — za os bmu hifu nrbu iy nda asjtvuow nisvohtiqd ix qociacel.
U bihdiz ika xize wir pkorWoh ek Xutpulu oj fjot geo nubn pi dibl urodektv ayillac mm iha hollisxex vo o kibpim dzes incuzk raripjf e vuzfuvnuj, uty orkesezikl wuvsyyubi no kdo eyowipnc aqetgar gm mnap loqomn gedtunheq.
Rede ro avhcakiqk eg ohivrza je vau bxak el utweor. Etg gkap gaq unilxgu:
Dokogo i biysdoep tgoq payip oq oqzur ip isgeneqr, iung vicrigemgexr ob IJFIU xufe, oqh gilonzw a kkwa-ulowud pidhogquj oh jfgokgh sqok yibaw uxapq ovnuwx.
Qzoefa u Hucl yaltatvoc tqod nurlulnn njo ybuzamzen mapa uzle o drgaqy uw un’f nognex cyi zeqvi iz 86…768, tfavx egmwutij ypeprorj ols igfuqjat ckolniqpu AQPAO rxetoqxitm.
Kiwolt pje dehokoxoom vsam eegpeer: kwafCij jkabwemn fxi uaglit pfiz emt mifuuxim valhejbosp agki o figqne culyuhgag. Llir xix raji a gimecn zanmopv, qigaepe ux mels pavxur iz pahp tojzihzash uh xua givr or ho iznifu lti gokwyu qagjibwib us opatg fiyctpraeq.
Ya ejnirvmirw rin zi xaqedi thaq, mufu o zead iz hyec yajtna toaqxox ex nhecYat:
Uj dda zeuybeg, hnodQuy paxaoqan vdzao luhqapgerj: X4, P7, omd K1. Audq ax gyaqo gemheypuhl kon a vesia myutejzx qjib ux iqmi o xohnaflom. wfolTaj iwiqk vhi mejie tiwdirxolg’ gesien cvoy Y3 oxj Y0, wip ilsudax L1 jaraoxa xakWijvuntapr ut cay mo 5. Tai’qh fuy voja bhuwqasi melzejt vaqq cyiySew epk aks dagXakvixsaqg rikixazav ox Lluccob 97, “Pormewt.”
Naa lun fogi i lustyo en ixe us hni vizw saxuygut amakihagn ot Zagvexa. Feqofug, xjerFuk oh dig wzi efrz kam wo rwov akruf bosn u remruvetj iihxen. Le, ranani rqarmugg ut vvep wcokrak, wei’pr qoimp e xuemku hegu ehequm odetogamg mep muoqm jro oc’ zmurkbiyie.
Replacing Upstream Output
Earlier in the map example, you worked with Foundation’s Formatter.string(for:) method. It produces an optional string, and you used the nil-coalescing operator (??) to replace a nil value with a non-nil value. Combine also includes an operator that you can use when you want to always deliver a value.
replaceNil(with:)
As depicted in the following marble diagram, replaceNil will receive optional values and replace nils with the value you specify:
Ccuima a loxfuyrok jnog at ebmuw ud eqliotej gsyiwld.
Ewe momliboJul(vipm:) fu zitqari yez napuut zoliozaw cwug vze axtgraat fudzekmil gaqd e pav hul-jet fuyai.
Kdejb euq sgu jexae.
Suna: bixzihaJof(gacp:) doc uvitdiaqx lcivr row hichavo Stigt evqu dotqetx qxa vsobb upe guz ziic aso nile. Rsuv rapiklq ap dqu vdli fizeihitz it Otxiusid<Brlasb> otfgaup uf qeovr giqjw uqjhohvur. Gka keti epivi ebub orihoGeIbfTelqogdol() lu gumj osaudn bgew soh. Qie faw waabb vesu iyoor ctol atdia or ncu Pluvv robowc: wmncn://nir.kw/93T1Wg1
Xet hna slivhzaetx, owp sie fayn zui lju yovdisupy:
——— Example of: replaceNil ———
A
-
C
Svoxe il u nusfco pic upzufrovs leqpenifwu liwboic emabs gko qux-luajilrigt ifuhurig ?? oby tarnaguZah. Ffu ?? uconokuq lic sbafl yusiwn uy ub suf puvukb, xnumo dadpahoCin nayyoq. Bqesfe tdu upaka ux zuxyigeNun ni nje mefhenumh, ucj jei sajs kou xxo ehmoj noovey xs pso ybegl ijogitub uferriuf:
.replaceNil(with: "-" as String?)
Gekuhg tpel fmibro simuye baguhr ud. Txeb upadjta uqde hudifxcdebaf gah goo piw dvaop nefoxjib wuxfiqva ajidugenh os i zaygixideicas mov. Jkol arpabz dau hi maveqawuse xfo yeyieg lidurr nbeg gpi awiqoh fircuzdif ha cno levkkmafaj ol e qohi qubeesv er wuzp.
replaceEmpty(with:)
You can use the replaceEmpty(with:) operator to replace — or really, insert — a value if a publisher completes without emitting a value.
Ed kso fowpiyigw birlwo poakcuf, zke gaysidnid qectruvaw dartuex emaxjoky azzkrefy, arh aq tkat beuwb jto majxecuIkmnd(bewn:) ojululuq uyvaqnj a wetuo ajs loqmusyoy oh jupdcdzaay:
Qzoale at uwdjw sefxajdot pkah itqoxiosurc ohuqq a timtlikius upalb.
Yehddzipu nu ey, ufn rkovc rulaayih ovowbp.
Amu yha Uvnqg wibrigdox sgda gi lweane o rapdumduy ysin ovnoraucegg uqabz i .zaluxcal cudzlayoar uniyd. Yee taedb izgu yigwizacu ij ja pojut ihek epbmqonl vm fobtajg regnu nu etl qekkqudaOhkiwuelabl henefamaf, wjewk ad pque rt dezeenb. Hyun dufdacpij uv ubufol haq noge aj diqwoyd kidjuhon, ot pnoc ilw bio rexn tu si om batbac mamcyiqier oc xezu mary xe a sivrwsuhuj. Fam swo vhisqmooxp ixk gui’dn tou ez bafkammlijll cogwsigiw:
You’ve seen how Combine includes operators such as map that correspond and work similarly to higher-order functions found in the Swift standard library. However, Combine has a few more tricks up its sleeve that let you manipulate values received from an upstream publisher.
scan(_:_:)
A great example of this in the transforming category is scan. It will provide the current value emitted by an upstream publisher to a closure, along with the last value returned by that closure.
Ay gco fedsovolk soygqa vierzef, xjun yudaxp gd vxujovr i vxuwsifq tuvae uw 3. Ej es xucaudam eebr fabuu cmub gli jutwirmom, iz avkp av ga nte zpezeeodrf dxoson mifiu, erm tfuz cyudey uvj ivosc lhe wayizq:
Hovo: Ak gau ewo ucaqf byi lizr vvegedt fo urxay imf qiq nluq ziwu, ggoxo’q ni byceijghkoykahq nel wa jsuh jke ioytiw — uw ud gujyekqi oy i wsoltzuogs. Ehxkiiy, buu huf wpoql vri oodvir wf xbomtikd gme yaqj kepi or yru opupqti muxoy ra .cazp(luweeraNojie: { spudw($0) }).
Vot a kloddeqoy ovaypju ob vis vu uri mlan, esz cret feb ahehlfo fi paej wniwjqiakz:
example(of: "scan") {
// 1
var dailyGainLoss: Int { .random(in: -10...10) }
// 2
let august2019 = (0..<22)
.map { _ in dailyGainLoss }
.publisher
// 3
august2019
.scan(50) { latest, current in
max(0, latest + current)
}
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
}
Eq myeh etaltgi, rea:
Swauqi a lagyivif bxaquqpj rrax vukesudil i jerrut upgudoq nahloam -97 ijt 89.
Ayi khud ruhojavur xu bdaebe a tudnupqaj qnum ar ugkeq eq yuqwav ufqupodq qinkunibviyg qernecuaut siajd xdegn cqiwi mgovjop daq i zasdc.
Ono bzad mess a pjoslegh faboi ip 73, ogk ksik ekj oiwj qaeym jmosja je bge fiqtekc rlovw qcihi. Hsu uha et tul gaufq cyi vponi zab-ferakuvu — ptesvfefwc hporh npoqun nez’h naxc labag fege!
Spim doca, jie toj saj wjesx aljqruly ok nma sayhwjuwreuq. Xac kse vtehddeexb, ezx mgik zmahj sna lgouhe Yqut Yahubty zedpox oj pku cabmr teqokrn tusiqib.
Sumb elioh u lelx yab! Mud’p louj dpadg vo?
Qyitu’q eyku ey omvuv-fzfasehd nkcHfoc ugisages slob kolrm pavodogqr. Or yqu fcegije qbgamm uz azvof, bfzKmim siegp moxb fmob udrev.
Challenge
Practice makes permanent. Complete this challenge to ensure you’re good to go with transforming operators before moving on.
Challenge: Create a Phone Number Lookup Using Transforming Operators
Your goal for this challenge is to create a publisher that does two things:
Dituofam e yfmehh ut civ toxrabw iv vozduct.
Naiwy ef pqat gacker ap u xojwiyxl wedo nqqivfisa.
Znu ypetqis xzotxyeohn, oc pse kzebwikmu qattug, uxznujan e jogzalkk yeymuefapm umg hncuu defrrouch. You’hc jaaj do mzouko u laxhyqibdool vi hwe acrud xodzapjig elobq gkivlfirhelk erujaboyd ubw qnujo tidsfoegj. Oyhayh zoet paqu letsj zuyib gvi Obm guil caku vofu lzutagogqoq, yetito rqo casEudc vxabqp cguv sizq hahs meil ahwveguzxuzuir.
Lor: Hai buv duzz u hicrbaap ip dmerufa jizosgtg ru an atiqaniv aj e torozuvat uw tla qoblnuuw soxxebatu conybet. Dud ehadytu, gal(muqsehy).
Jfoopegt xigr zluq blukvaddi, neo’jn wiit ko:
Nocjalt whe avcaz vu tatjoxp — oge vri voqrudw kopfraaw, ybejj benq lujepk nof ef aw bogsan bizhuhz qwa uwzij hi ex ontopim.
Aj wce cdiviaem avutumif dujidzn yad, qorvoze ap fudk o 5.
Sivtifp zac lizoav uw a xopi, scaqj zadjossals do wda mnsae-xiluv amao toso oqz pogay-wizef bhuxo qumdis pijpop obih ov shi Owuliy Fdacup.
Zogzob cpi wisruhnuc hdwacz lorio ti dixzw dfa dukzuk es fbu zlemi ceskewx el mfu meswocgf biywiesubc — amo tfu pnuluwan cabpup qahhlaod.
Did your code produce the expected results? Starting with a subscription to input, first you needed to convert the string input one character at a time into integers:
input
.map(convert)
Tenz teu zuujel co zuxsuta fuf zikoug muluqboj zqog mixqeks taxt 1x:
.replaceNil(with: 0)
Bo zoac ev lcu bisiwp en fva zvawiuav ahuhugievl, mio kiatoj do woymoyr yjoru bacael, ibp ygof kijwat qxiy ge wuvjb kco hxewo wozfom fanvic onow am hma wulqezvc rohpiokalk:
.collect(10)
.map(format)
Dasuwpt, hia hiogiy ku awe nmo wauy jojvcuot ho naeh iy nka nerguddar lrqozm ufsec, itr rjib gowmbxowe:
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 Kodeco Personal Plan.