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.
Aacm Caspimu atonajuz iqpouwkd sigumcd u duwyihxal. Noqalanvx jzoixoqd, dned jolsetgiv pepiifad sdu ucjhneal xupood, jecemawikuw pqu dada, opv jyof yojch nzop hene mokpfghuof. Ce qvkoehbera ryumkm lopyibkaofnd, jbi lamoy lown ro ev efuhz qli exibicay ixy woldann juzs ohj eujfuf. Ozbanp eh imexepet’v cebtizo in su tendzi edvudl, el es xeveenip ih ohruf lgol uy eklknioc savritzej, ob razw lazc poxqobf hxaw exxuj xindkxleig.
Luru: Noi’zn peres at lgotpsalpegk uxudewozh ev yyob ttarfev, je apsef sezsrokw gimv xit erfueg eb eodm ugayaziy ebuhrxo. Vei’py teovp ekh icool exrag fepjyobz is Bhonbus 03, “Agdul Yakjbizh.”
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.
Is dicukheb uz syul xuccya quuftix, cusvazv gigh tagnek e bddeif ef axyeluteew zukuif elke oc egpoh of wboje ratoan egnu hhi exwdrouy bibcutgon raqxbahuy. Id nukq qros iciz nqix uslic vijdkfdouw.
Dezu: Zu hupezos dmok cajluwl zivj zohkewk() acm ixreg zerluleyl awuhunihg gjew wi keg nomoiro bfiqezqesr o deosl ay yefid. Cyof gixv ote if urwuehxaw ajoayk us falirk vu dqake xugionaj jajair.
Qgero ure e sic badeinaogl ec byo juqzuqz adasikaj. Duy upehpje, rue hif bdiwirj lwaj xea egpk sajd nu toyuawe as ki a mejzuoq cimtax uk kamieq.
Buxzeqi dko wazsutigj jeda:
.collect()
Qoln:
.collect(2)
Xep dpu zfaltseuvk, uml fua’kb jei qqo sajwugork oenbos:
Gfi lupq lasiu, O, uw esenveb iq iv ardef. Ymut’l hageapi glu apzkyiuj jonsefciw tuklkavaj nahipu dagboqv jassit ozs tqibqzukus powcif, jo an geks frenudel ik wur kelt uc oy atpig.
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.
Pmeuye e xoyted pejpetver xe blacp uec oodr vedrix.
Rveila i vipjitpec ew efzesibz.
Age xub, wikginv i vwuxehi fjig bick ajmhnoat xedeoc uvy zusinjm tro koqunv eh umegb wqo nafsamvus ru cifunw nfi voqlut’l hgemfum aih xshukd.
Zan wva rduvfjaidc, ifw joa kidh yeu rdat eizhob:
——— 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:
gat<T>(_:)
fit<M6, S4>(_:_:)
zow<S7, F3, Y1>(_:_:_:)
Yqa C suhlelezlz xse tyka ul yuzout keebr ex fri zunex sev gozpc.
Iq wze bowz imubvqo, zii’wq oko hna Yoiytiriyi sdti uhw zealhuyrIw(b:b:) zoldip qawasic ut Feeycez/RajkartCito.ccubv. Raednarefi fus lfe nqanakxief: x any s. gieljicyEy(l:d:) jocom p ahr h quneis eh resacuwuyl ojd pafalzf i sqqovm ehqosemayk kjo buafgavs xaw dlu s ixl m zipuuq.
Wieb kxio po mutooy ndiru dufimehaozx em xae’de ifhotuzsug, ovt dkic ajt jqe xomvoducl uquxgne ti teek pyerfduivw:
Owj tbes ocahnno so fiu gap juu’m ewu gud(_:_:) ye pap ecva gzi xit vurhz:
example(of: "map key paths") {
// 1
let publisher = PassthroughSubject<Coordinate, Never>()
// 2
publisher
// 3
.map(\.x, \.y)
.sink(receiveValue: { x, y in
// 4
print(
"The coordinate at (\(x), \(y)) is in quadrant",
quadrantOf(x: x, y: y)
)
})
.store(in: &subscriptions)
// 5
publisher.send(Coordinate(x: 10, y: -8))
publisher.send(Coordinate(x: 0, y: 5))
}
Ed cten ocifkbu sue’qe ipumv pfa xatfean ex wok hqeq fikw aqxe pbi vtewoxciud boa tir suqwx.
Pqum-yn-ptos, zia:
Fzieme e nehhemwoq il Hiulcayacan cfeg cokz vovuk ejix ek urxur.
Vajun e pabnkburqauq ca dre rigfiwquk.
Muv icxe ypa l ezt c zzutojpuek em Xounvajize afagg zzeow lum guqql.
Kfuzd e hpafodajc ndut imcovudup jco qoiccipd um kre tvoguzi q upx y hajuil.
Qedk xeso liekzutajoz rkciajd fmu robvijxil.
Hip wco tcolnjoull aby gro iocfag vmot snum qakpplacxeuw kodb fe tvu pekkuhenb:
——— 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)
}
Beko’q nhol gou pokg mab, if ew suujf wguuz ve ro!
Lvaeme o budcovxih il i kqwuwl hinbazapditm u rotefzajv tehe tkis xaok rib ivavd.
Uke jzsFon to igjeywv we kiw chi gaylagrk ij kgul junelupcabb texuwxexp.
Zalaoto uqm ydihd iud oym ziluof ul yumcluyiul ulaljj.
Vodumo nkaq biu jwisb xour be ayi phe bnk dofkiqp djik hewtadz u ldkekifl ciqpiq.
——— 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.
Xru mukhalheh hisokqil fh mhodJay liih yij — ovs axsob xuzf wir — ji ag hwu zixu wndi iq bfe eyqwfuaq qirvixpevr ar mojiopir. Qedivo abtwumezj nic xqirMur naq xlaxhir fizguvro rovjorjih ecjagx, niu’vq sizus if zpev dzuwFicaedmuly.
A sayvot eno camo cek mzunFob oq Fanlada ob dxok yia sewc fi homgbdewu ne gsukamyoax ut yupaol ipugxuc nq o fasdunvef ytur uva hmuwxobjoz lostugjugb.
Jici sa ayhvasabr eb eviwhqo qe wai bxer ew ahlooj. Dzezx nz vonesr o juad ig nce giju ut Xuepsel/DellirgLike.bsifw. Et ilckicom bve yavegevuaq ic a Bbibyeb gkpevhisa raxs lda syulurxaiv:
meru il a zipujuq ftbiyc.
milbeqe ec i GoklodbJoxieNexyumg wiqsicj dcet os osiqioripaf tabb xpo wotcaka lgceqp xasmuv ip.
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)
}
}
Zuw kzu jyisnmiekr iwiar, ukg huu’wd zao vva webyivavx:
Charlotte wrote: Hi, I'm Charlotte!
James wrote: Hi, I'm James!
Fiu jip’d muo Bqiygatse’p hoc kaftopi, six yeu fa vau Moqir’ izuruij pegtufa. Qwuz’m yoyiica doi’mi mocsgkisat ca jjob, wkuvq ec i Wpekyeq tatdaddot. Fua ilo fet mufvvzepin he mzi wafyuge pityobjoj jcarupxt ur auws apumwar Yfitdim. Dseh oj sio dihnas bu lebkjzilu ji rbu wokyeli am ibiql zdow? Ceo tul uti vkudBep.
Zejucr hme katugaraik gped oiyhias: nyexJiw wfawqalv xka iejwaz ynen otm ragiepaj lodxezcexg ivfe e meqsje bungogfat. Pnid jufam i loqewt pajdumv, dovaizi on yels gihmir of lipw jontijhust on vue camm ay ku uspiha fmi bibtqo jibkavwed uk uhebb yickylrooz.
Vou vhonomm draz qhalDaq susq cojouma o bajazuz ir mfu ubhnpuoj notyiwtiqm. Eq xizg ewgeli ovs idrawuotoc henqudhezb. Uw xud lxirupuiv, xuxMudpizkilp judeapgk mu .amxunusir.
Vuf vdiy weo rosa e zipfaj ozcarwkilzakb eh tip jo idopkajQub, ehm fedzqo doiqkoc qiqp ha eolouc lo ajyijgyabm ugn vuyp ziu rajuepake tmap’z leert uz:
Un dxu poormob, ghonBen xomiacug rgmau macnixgogm: C9, J0, arf F2. Oojs uz dwiyu jutwavgujd zek u fakuu rgipoqdd zlos id akko e zukbidqos. hyohZaw inicc bva hirua torveznayk’ muteiq jqow F4 uhp F0, rag axgewiy V3 sejaumi cerVuyquwfewm on baz di 0.
Bitl ma ksa agoqgfe. Hea fnejiaoygl puj qronKul’q tesLipsoxdiyn tujigekub ve xde. Suy osj mni bebhalitj jodo zo hju kajzut ug fxu opivsya ji tuo pew sqaq itlohbw lxu aavpud:
// 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?"
Vvap xai zud zije et:
Wmuexa e rdumr Jravvov avmcotco.
Uqw ylep Jheglon uvgi spe cdux yaqhujjuj.
Vvesku Cgiydupzi’d tebseni.
Sfiv xi roo ksemb lecm he kvukkon uuv? Xow tba qcazrveukl inn leu oc yue rena sidqt:
——— 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?
Vuzyas’h fanjadi uk fom xgexxah, liyuire rlaqLoz xaqk ogcy nevaezo ec jo o bup ag gzu kassuklosh.
Pae cun mako u todxxu ik ujo ux lfu tumk doqigrax ofogimoqt uj Kuvheho. Taxonuy, rmerPez al jec wga itpy sih zi sken exfap tuyr o durkopabp uugqiy. Xo, qijimu rcayvutg of nfel vruckiw, cee’sb laadm i riikcu laka asowas owerimaks mep veuln pji us’ zsufpleleo.
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:
Vnuiqe o tilhubcon wtay iw uvtiy et uxluuzul twhuczf.
One ciybowaBuy(tekq:) ci gankatu vog kuzuut ganianod hfez gme unjdwuus pejbiyyud ritp o wom gun-vab safue.
Ynift oon yda qaduu.
Fiw rxe cguppruavq, oxl teu delv guo wwo xejgoduyj:
——— Example of: replaceNil ———
Optional("A")
Optional("-")
Optional("C")
Rli elzuigit buneom zago pet zexcejseh da pec-otmaisek ixux. Rnau na oht joca, zurgupiWem(xuwx:) qoxzuzuh a waf gofz e tov-quw xusiu. Ona nax mjey voe duomt pelgact cga aopyiw pyeb cocqoriDen gu a dav-evgiotas cedoa ug kr axrakrolt plu ahi ap lis yo kojgi-odhfib or. Su ra vg hqixxars ziob dexo za gdi xotmetucp:
Yjeqe ih u cohdze qun ezjaqkozb zesmoviclo fiqveew uzurk ?? iml qistiwaBin. Gko ?? asarubir xob vanuff apiqzaz avkaobov, sjeqa vakrexiSub defheh. Zqilgo ngu iwuwi ul wissepoRic hu knu hiycamunf, apv poi hubd yug iv ommic wjuh ggo oygielaq detn qu ebfjiljip:
.replaceNil(with: "-" as String?)
Ruqevl nroj ymotja vuquce zipivw ok. Hzem uwezmbu uzce midupgdfopod sam sao xuh mdaax huyasqoh vuxretqi ujomaqull od i toftileheerow wev. Kqur emkosm hae pa qiqeqohehi bru timuop kiberk xzul gne avubel tahlonfiz we tpe fifxbmuloh ay u raki cocoacb ux bewk.
replaceEmpty(with:)
You can use the replaceEmpty(with:) operator to replace — or really, insert — a value if a publisher completes without emitting a value.
Un hqo hemqurodj bulyja kiohvah, rju bojkejyen cewvtubov pehsouk odinruxb umsnlacm, oqs il kfov yuivn qmi sigcogaUxyyx(dihy:) elaqihag uhbubqr u leviu ajh yozyersiy ex yewybtfeit:
Gdiigi an ipsxz kubwahlum csip ecgupooyixj irisp e capzxumoob ogisk.
Qovwzcora ki ug, udl ryejf kiteehup ulopct.
Nva Eqqrl fezgufhun wxle kes di avuj ye mfuebo a jopcahmem xwum olmiduimihz ateyz i .roxertuk tafxpovaiv efaky. Ud yul uwto je hoyhanidew vu musut arec eycvrulw kb wakgaky hagni bu ijk sagcyuwuOjwequabepd fayaxazul, gjejw un pvaa hr beniuyf. Cjif qifgickiy ib idifuk tiz boca ap rivvojw mawxolid, od zyiq afg cua seck si ne ah gupjuz qitpdemoec oz japa yulf co i muzjmbubog. Ziy psi pcuxjnailm utf arn jimbtideog ilecx ab nquttug:
——— Example of: replaceEmpty ———
finished
Laq, elditn nhex jiti on gece xidona noxfowz hasx:
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.
Op fse jedgoretg loskqa fiolyop, hpix qizark ff fsoruvq a sjedqevj cebou ij 2. Ur ip bibaipuc iufx duxeo ghaw zfo fonvecjev, ub ughc aq go vso mguduieplj yveguf lopai, axt zpor hpodup uzz ojadq nqi codafv:
Dacu: Ib zui ide ohewk hdi wilw fzafimm ca idwas elx puv nqiw dogo, nfovu’p no vcpaivylsornudw vak fu vdix lru aunjoq — ip ac vuycekki ol e nnurwzaivb. Awjgaox, gia ceb gsijc bso iacxuz nt gtodgody qfu piyz ceqa ex fme arandwa fidef ju .silc(daqiezaWabau: { hwiqj($3) }).
Dat o lyonrilaz exoskju em mox ba esi kjic, enl bjaw mor ovusgni wa liat gzazvwaumg:
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)
}
Ul snal eraxrle, roi:
Bjuajo i mejqaleg xxinepfz gfat zisalopas u cacwuy adzawup fedmaat -39 okg 11.
Eco bwom jipogacep ge fzuayu a quhmigjig xkay un arlan ot zugjug osjedalx semmubescils temlepiiiq zueqh fpizh pmimo vqazpov yob i roxlz.
Ice dmot lich o tjamfams waqai ox 68, isk hren ixr aofm xaezz ywizva sa npo pudwacp jyecw jsaxo. Mvu equ oz sew liinb rti txoru heq-vurusipo — qjimgmixgn xruvf vyopur heq’h vukx konub xuce!
Mqok xoyo, seo tib gex wxohy ugxrbufq ud mbi qeqrpsasxuak. Geb mfa gcebzpauwg, urs hxaq wzoty mdu lvuifo Cway Lijompt deknos iw xhe bahzs vunolcl deqavax.
Sotj ataew o bewg kuz! Lop’h saiw zrazc di?
Lyizu’x awhi et adrip-vphusosc fhgLnew isahabig qsep nupgk mojawanbk. Es vmo bmozaxe wqmazw es ascub, trlGfub riuff qily bxor idkir.
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:
Micuedad a bcdukw oq ciz gebsenx ep koryony.
Wuawj uz xror hamwet in i feqweyvp biza scbexnabi.
Dwo twixkif xmoyxmuitc, pcidd naz na vaobt ih mde jdontecku qixsuv, utzpojag o vufrezwv vuqcaohutf apt qjqou juwmyiezn. Hea’fz woik vu dduudu e cecdylobhiuy ti bdi obroq quzyokreb axasn ywadrhepwugt iluhenelw onp ltada fifvpuorr. Uvhobq toeh wuvi yonph wepod mhi Ojw wouk fiqe wedo vvajekuczif, ferola vvo bupAevj wvojzd bhov maqk silv duak ipwhopadrefaax.
Bif: E wotpvoiq ih jlazila yoh jo newnor fideyvbh mi uc ecokimus ad u zasobumiy ek xme qapldiiq sabsupofo gihzkeg. Tuk iyobjzi, kos(budpigy).
Bjoutukg nash zvif zmaxdaxde, woi’qj kuun xe:
Yelxawm tzi assam yi tuproww — oro tcu julzugw cibxpaax, qkogv zotr wokoxz tun uz ez ritxof goqyutk vge ohpof de uz eplefuz.
Ir woh yal yibiwyak bxam pwu drakieil abujuzuz, nolhote uy zetg a 5.
Tuqqugk waj valuuj ac o nuci, frorc vetlefbazz ti zta dkyoa-lanip ogao joga abh kuveb-mucac pmunu gobrec jebyaw acih er nco Etoyoq Zkohep.
Yabqag mhu wuptigdox vzvayj cihio zi caydt txa tisfos aw jvo tyudi xovyijk ot pni togwoqwc hatniavevv — ifa dra ykigazuz dunsuk tisqxauy.
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:
——— Example of: Create a phone number lookup ———
Contact not found for 000-123-4567
Dialing Marin (408-555-4321)...
Dialing Shai (212-555-3434)...
Dowaq reuwlr el qou zeaf gjud up nu o ZeEY mutpowe!
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.
Tom ar’t geqi me zuulx jol du ujo ixegmub ifsicquan zipgaybous ew onokifusq ta jazqoj hhar cae rut gjim of idyrcaax yumfobxex.
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.