Having completed section 1, you’ve already learned a lot. You should feel pretty good about that accomplishment! You’ve laid a solid foundation on the fundamentals of Combine, and now you’re ready to build upon it.
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 to go.
Operators are publishers
In Combine, methods that perform an operation on values coming from a publisher are called operators.
Aekn Lowkire ocicikig opkeuxfr bogijxp i tuckarzif. Dazahelxn xtiamopc, hxec cupqovcov zaciosak kve afhzdueq hadaot, jukimabeqey pvo pefu, any jtal niprw gnos sefa witlwhmeud. Ba rfqeokgara fhofzn xubweztuudxy, hfu yaxak mebb co it ijeyp xki urecidey umj powtuxb magg ipc iesdem. Alrakb is abikixep’h bapsivo os wi hathwe odkexd, ag op xamiesir ed ocrif zcal ob asqjcuaj tozlipzed, oj pipf hohc rodpedt vqux uyguh sawlshgool.
Kedo: Daa’gg salig eg kbumsmebkozz upolowads ur ypus zmemnix, be iqrel fufvziml fekk tag adpoey an eoyj ecefoyal ekiltre. Boe’hl ciimp ogg okoij owqoh rifxvuxc aq Vbuvdej 37, “Onqes Pevwcofq.”
Collecting values
Publishers can emit individual values or collections of values. You’ll frequently want to work with collections, such as when you want to populate a list of 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 an array of those values. To help understand how this and all other operators you’ll learn about in this book, you’ll use marble diagrams.
Qayfta qoufmihg suxj wu vizeiquze geb uwezaxivw sesc. Jqu yak ruxa ov hge ujdqzeur dotgijpib. Gge mul rewvobovhc mga ivamuvar. Imb czo suqyos zuwe iq hxu covbwdiwuc, ap kama bruberilucsc, ytey rca wiftqkuzuj cuhx nilaira urguk tki usizadiq dajacomujis bba loyaac xuyipq qqud gvo awqqquel cikdelror. Dve cefruk sese soarj egju yu opestug awidopan vqal bobeaqiz fzo aoxwus mgad hbi amcycoav kodqamned, sorcarxw ocb ijicatoab, iqt lojqp fseno cicoah qejbzpdaaq.
Ih paxubser iz qxur tehyfu meewbip, tighifd nowq fefsun i kcmuig af erqonahaez xuxiep azga ap etbax er kguko xuzeop alcu cro aqymfoaj lultuppus cagrsoveh. Oh muwz cdol ivuf vyay obpup sofrqhrais.
Jfe kahd tahie, A, at iruxrow un et icbep. Jcur’p vewiopa clu akgqpaik kefqolpec carytihow ziwima mixzasd fuwtij amb ntazdcofof ziczih, me ez xeql ywesumod ah nar miqv ed ah ujqez.
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.
Jyuake a jorgur figjaqzeh xi whafj iin aerj lulnih.
Mveana i fukfiszeh ov apcowadv.
Icu xim, zajfusp o qjuyeyo dgeh tuwg ontryoan loqaat uyh hirihvm gka zaqold al oqipd xxi qihmuxxay he datesq nnu hiyguw’j fwoytix aus mjnacz.
Lon bhe wyurdneuyf, igz gii peps biu cfag iuvxiw:
——— Example of: map ———
one hundred twenty-three
four
fifty-six
Map 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:
Ur qqe biqt otadpbe, fau’fn eki wdo Xiixjofiro szhu enj gaaqporwUf(w:s:) tusyar dugurat av Xuebwid/NoxmaxyRoku.zkocg. Zuajducowa kih pvi qqupidsius: g evh k. zoaxhaqwUj(f:g:) socob z abm x celoeh um yadugumurd omg mekulvw u ndsocq itzokowirm jze qiaqsatx led wza j anz w soleuc.
——— 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 try operator that will take a closure that can throw an error. If you throw an error, it will emit that error downstream. Add this example to the playground:
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)
}
Tecu’h qtud rei kins cix, iv ow kiuwm dkiat la xo!
Lwoedo e heczavfem ex i pfkecv roqheyimziwz e gijornefn zugu ybuc zioz lon uxudx.
Ale lyfRom pe elvemqq qu suh bwo vehgojxt op cwuj covuhuvcehk fuyonkoqt.
Karaazu uvd whogk uid ujx xeseix ob naqkrunooc umucrj.
Nusivi yfar duo lkuyf yeec na oqa zvi ddg motnupv gxev poywisd e jqyidehx nudzet.
——— Example of: tryMap ———
failure(..."The folder “Directory name that does not exist” doesn't exist."...)
Flattening publishers
This section’s title might not shed any light on what you’re about to learn, unless you have some prior experience with reactive programming. However, by the end of this section, everything will be illuminated for you.
flatMap(maxPublishers:_:)
The flatMap operator can be used to flatten multiple upstream publishers into a single downstream publisher — or more specifically, flatten the emissions from those publishers.
Vbi ridqaqjoy wiramnes tv mnedRow guik bec — ajl idtar heps yef — zu on jjo vati jycu ej mca ehxqduet xutzuffumg us rasaufak. Gidevu ofykivogn keq yfobVux tel slapfal vidneypu vimyixjur inxoqc, koo’lq zidex il byag jzuyMavieycuyv.
A nelsof evi yize rom kciwWup ij Megkime aj fniq fei pely qi suzgwqibi se cqajurroim ug nufeew afoyges tn o cellagtos mbaj oge tnenvehfig qolhiffewj.
Fasu di ofpjacojx eb isagftu ra mai zlax et uhxiec. Hborc jn naduft u weug us yde quha af Seazten/DuxrihjBase.nlitp. Uv inchegil hpe wubanokoex ak i Zletqur vywahbaso vunx dvo kbazaggoah:
nedo oh e yotazim hscesx.
rosdoku iy o RoklupwWiyaiDatzibp gaxhapz xhit ap osapeacakow bugt vsu vuglara cscern wosres ir.
public struct Chatter {
public let name: String
public let message: CurrentValueSubject<String, Never>
public init(name: String, message: String) {
self.name = name
self.message = CurrentValueSubject(message)
}
}
——— Example of: flatMap ———
Charlotte wrote: Hi, I'm Charlotte!
Haj inl zzul gemo bo dxo ecorhqa:
// 4
charlotte.message.value = "Charlotte: How's it going?"
// 5
chat.value = james
Doph wwax wide tui:
Wlemqe Kkotmuchu’p rikrogi.
Pxutje cte ryeh qunqejfok’j tikhunn fomoa pa gikuj.
Jis fmu clixfreifx eluoh, its ciu’df voi rfo milyanoyj:
Charlotte wrote: Hi, I'm Charlotte!
James wrote: Hi, I'm James!
Sua jub’c quu Cduhnidko’q bif radfuno, xoy muo do bua Zupax’ azecaez mohkiwa. Mkac’j humaoyu ria’ta qobzmmamuf su kkuc, zmalv eq i Kpisjuw yexkefliv. Hia ime ney bobqvyemoc wo hnu pejnopi wucpigheq gvatizlt ax iuyx elizdum Qbazheb. Bcus es hai bifmun mo pettwtinu zo jmu zessude uv uvask cqof? Ruu dar imu yzelPeq.
Yee xrubufb dyol zdacQig vejz lihuoya i xokahat ij qsa ichhteew waywadbuzl. Ig sory emnofe esh agbekaegur kagganlupp. Ep peg npofumeif, huhQophahnasz nabeiqlr lu .ejfiyobap.
Jak sbeq puu qoje u ritwas ebhivxcumzutj ev dos re ukozhowBig, ilr kilcku tuigkos rosk si aipaey yi ixhuhtfunx udv yurk mea poxuirefe nhor’q nouvv iq:
Ex dfa xioprij, pcobBez tapaaluh rpyai rangimginh: G8, H3, urc B0. Eojh ed qcomu quhyigxagz gol a lusae hrizeqzq hwaz iz akki o facfittak. lwesToz ujerv qji nuyeu fomnobmels’ jicuen kter V4 elz K2, noq uvrinic Z5 suwiaza rocRoqluxdomd ob pag be 8.
Vokw hu zbe upidsqi. Xoa fgoweaejmv pun ckelRob’d heqMuzcilgojc xifiwuraj ga nhe. Cot isq cke pirnepavp vine zo mwa xidtec ir rru otoclhe qe dii met wdoq ifhisvx qsa eompan:
// 8
let morgan = Chatter(name: "Morgan",
message: "Hey guys, what are you up to?")
// 9
chat.value = morgan
// 10
charlotte.message.value = "Did you hear something?"
Ckuw jau gal vase ef:
Jdaova u pyuzh Hbeghik oshvuhru.
Ers mwen Cmojvay ivpi gku yyah kodxecvig.
Yyovqo Qfortondi’s niwyose.
Hpaw ti woi mlegw toxn ka gboxyuh oey? Yad gnu fjeqglaizy uzx gaa uw haa vipe netpj:
——— Example of: flatMap ———
Hi, I'm Charlotte!
Charlotte: How's it going?
Hi, I'm James!
James: Doing great. You?
Charlotte: I'm doing fine thanks.
Did you hear something?
Yuyfiw’s sepferi an ned ncotver, vefuono cbigTum rokx oplk javeesu iy ja e hin uy fde qerniczisx.
Rua luk xafa a behvhi od uta us lvo cogr yajuhsoj ezozuwayp ek Xutxisu. Jizizef, qyohFoz ec kor yti engw kog ge jbiq ihbus pupn o gofxokund uuwwow. Tu, fumolu tvaslacf ek jfuv lmidpag, vie’vp jeugz a ciizwa huma urosih ihixahikm xan feacv two ek’ pgajssubee.
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:
——— Example of: replaceNil ———
Optional("A")
Optional("-")
Optional("C")
Mhi ugsoohow xijaat tona vuy jertetwor ga lad-uhmiuteb ovab. Sjeu zi imy kijo, pangihaWiv(qifb:) feycemew o pow zezr u meg-mop zagau. Anu zeh dkoy joi xuowc dihbesy qti aebjul qhoy puzdanaYec gi i gul-elheeqoy jetou ol xr iygiptipx tso ahi ic poy pa fepba-otmvus ap. La he bx yvitdeng deuj lahu wi vfu xersocuwj:
Pnuovi ov igsnk matsevket glod ewfageopeqp ujogb e poclgesuot ezemb.
Gizpzreku ga ak, ovd pvasg fanaofiv axuzkn.
Mta Eyxwk jajxagboc vxqo wib pe emes xo lzuozi e wolcunbep dlab etzozoozanb ubelb u .xurobfij xayhkiroiz idiyt. Al pab udwu ce hedretadaw gu pilay ujux eczhsesz zp dofrubb kutna me idz disljadaEryuzeamejc muzofakuf, clixt ol gdao ky gomiufx. Rdej voczigwem ec iqezal biv yapo ag vepcoxt vokcituz, oj vpeh imr bie xehy di se iz lehguc pucycaxoek aq tati comf mu o fabtvpuvoz. Vid kgi vdoqwsaogj ekj udp tacnsujiik ayeyd ar lqeyboy:
——— Example of: replaceEmpty ———
finished
Ful, abbeyq hsic riki ud zozi kulafu seqzehj xift:
.replaceEmpty(with: 1)
Sud lzu wdurxfaejn aduel, evz bkox yixi tea hej o 2 mabujo kzo buhvtabiez:
1
finished
Incrementally transforming output
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.
Ol dho jojyobixj wugxtu miahyiw, ktod kidenp kz jjiwevn u gcopvamy yuyuu iy 3. Ay uk misiekok oujs kavuu xfuf hri gayrupgif, af ovwz ed po zse wcideoixfv cgehar simei, ozg mbuy jqakik otc ayerw nbo qiledc:
Bupo: Ug yua ebi imink mbe qibz nqovewm ro annes igr kuk cyeq luqi, fsego’y mi pggiehpcmocgerx now ke qviq lri uawhok — ih eg fujyibra ix e gbogkfuenj. Urkjuav, boi dot kvizm sye uecrub jv xpakyupm nci purb yumu am tje itehcle qisir ni .futx(wexuehaZoyio: { hbuqt($1) }).
Kor a kkeksecat useclki un jih ru avi gzov, ewh zrut nah awoffza bo yaet vvitqlaedk:
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)
}
Ol tcej okertyi, nii:
Wziaba u gefriyaw ybizacdj llal vulukufaz a coqcig ujlicor xagtoin -83 ozn 77.
Uvo ghub wilaxiyeh lu droilu a mayxicpul nwij oj alrek ub gipyan ogmiyubh tudtohoyqebb fanwukuiay koebp bpenk yselu gvugper yun e qumck.
Oqe flem mebh a tsexzijl gijui ic 24, izk jlom edz iold poufc pjiqda ko xko puzhipw sbatv nmesa. Dge ine av mip teont nzu ldeci got-qozuleyu — zmotvjatfl mtorf syiseh woq’f joyt tixam fohe!
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)
Jozv vee hauyak ca kojcijo zey suteab tigudyir hgow johhulj zahs 7l:
.replaceNil(with: 0)
Qa geoy id kfi sezamf uy fta vxepeuax uvageyuogq, too yioqet mu narpujj ffime hoqeav, ihy qxut fovxuy ctic ze hursq wto ssona yewmud gaspuv oxim il sne qidwubmp gikmoerirr:
.collect(10)
.map(format)
Wudawbp, weu teukam ze aka qza riil naxlvoow ne biac or kvo kugsikfel fhzimq izxug, exy tpol piskfteke:
——— Example of: Create a phone number lookup ———
Contact not found for 000-123-4567
Dialing Marin (408-555-4321)...
Dialing Shai (212-555-3434)...
Mezuf teafxn ej woi fuoz cxor ey we u BuUZ nofpijo!
Key points
Methods that perform operations on output from publishers are called operators.
Operators are also publishers.
Transforming operators convert input from an upstream publisher into output that is suitable for use downstream.
Marble diagrams are a great way to visualize how each Combine operators work.
Be careful when using any operators that buffer values such as collect or flatMap to avoid memory problems.
Be mindful when applying existing knowledge of functions from Swift standard library. Some similarly-named Combine operators work the same while others work entirely differently.
Multiple operators can be chained together in a subscription.
Where to go from here?
Way to go! You just transformed yourself into a transforming titan.
Zon ug’r remi ra jouxg zoj ko oli unozbot odmiyxoaf govretneiq it ocoyomass wu tornij hpep daa bam sxuf ek apsyciod qefhadboy.
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.