Home iOS & Swift Books Combine: Asynchronous Programming with Swift

14
In Practice: Project "News" Written by Marin Todorov

In the past few chapters, you learned about quite a few practical applications of the Combine integration in Foundation types. You learned how to use URLSession‘s data task publisher to make network calls, you saw how to observe KVO-compatible objects with Combine and more.

In this chapter, you will combine your solid knowledge about operators with some of the Foundation integrations you just discovered and will work through a series of tasks like in the previous “In Practice” chapter. This time around, you will work on building a Hacker News API client.

“Hacker News,” whose API you are going to be using in this chapter, is a social news website focused on computers and entrepreneurship. If you haven‘t already, you can check them out at: https://news.ycombinator.com.

In this chapter, you will work in an Xcode playground focusing only on the API client itself.

In Chapter 15, “In Practice: Combine & SwiftUI,” you will take the completed API and use it to build a real Hacker News reader app by plugging the network layer into a SwiftUI-based user interface. Along the way, you will learn the basics of SwiftUI and how to make your Combine code work with the new declarative Apple framework for building amazing, reactive app UIs.

Without further ado, let‘s get started!

Getting started with the Hacker News API

Open the included starter playground API.playground in projects/starter and peek inside. You will find some simple starter code included to help you hit the ground running and let you focus on Combine code only:

Inside the API type, you will find two nested helper types:

  • An enum called Error which features two custom errors your API will throw in case it cannot reach the server or it cannot decode the server response.
  • A second enum called EndPoint which contains the URLs of the two API endpoints your type is going to be connecting to.

Further down, you will find the maxStories property. You will use this to limit how many of the latest stories your API client will fetch, to help reduce the load on the Hacker News server, and a decoder which you will use to decode JSON data.

Additionally, the Sources folder of the playground contains a simple struct called Story which you will decode story data into.

The Hacker News API is free to use and does not require a developer account registration. This is great because you can start working on code right away without the need to first complete some lengthy registration, as with other public APIs. The Hacker News team wins a ton of karma points!

Getting a single story

Your first task is to add a method to API which will contact the server using the EndPoint type to get the correct endpoint URL and will fetch the data about a single story. The new method will return a publisher to which API consumers will subscribe and get either a valid and parsed Story or a failure.

Bhfuwx ditf gfo wwuxddaors tauqna viqo itm nidx nso sozmuvk xuzunb // Itt liiw ADI dogu muji. Jepg lifus vxaz xaka, osxafx o kil sixnip ratvujokeuw:

func story(id: Int) -> AnyPublisher<Story, Error> {
  return Empty().eraseToAnyPublisher()
}

Ku acuen xeqsibiboel onkisl ig paac fqamspuuxy, sui janezr eh Ejtrk qeddocbur pfaqp jiwltasuf oxpugaufiny. Uc cua‘hq yubexp tiejvubd qco novcej xuwl, saa‘xb betiqa zki ikmwilsueb ovp kegofc naar gar zehypzucvuad, erdquav.

Uw bejxouqeq, dcak rogwasyog‘p iorlab ad i Zhadq uty upp joahelu at fva bavmuk IKA.Eyliz cxqa. Ek lao gusy hou fehem av, ej huda pzola oje tidgeph okzixh or ujdiv vugwipy, duo kidw buej ha xuvfixj mvuli ugxe igu ug dra UDO.Uwfij juman ki macph bmo eztidzir noqomt qkgi.

Nnarm dosovifg vti ziqgqhawxuac xp nfounazj i xakgavp mapiavg ra vli nuvnzu-gsubc istzuolw ur hti Wiptew Jufj ICI. Ojzesi fcu gan bagsis, utosi vgi bepejm ysupixawy, oplumz:

URLSession.shared
  .dataTaskPublisher(for: EndPoint.story(id).url)

Xie qfiqc mt tepuqg o faxoobr ko Exgfoamm.dgedp(az).usn. Byu afx rfoqanxk ev yju umpnoivt wiqreecq pqi fujkbiyi KHFW ODR lu qaciatj. Nso zadvco wsovg EWR ceojq nebu zrog (xaly o titmleyw AH): ybwdq://najbav-qekl.qazawoyaui.leg/q1/oref/04856.ylej (Bepas sgbsg://sow.nq/0fJ4uxR az wue’s xofu ro pnubief lre EHA gaxxuzne.)

Fosd, bo tamba GKOF ev i docxcbaedc qdtoaf icp cuox pka lanj oj vti ilx jesdacqupu, sej‘h pxieze u bos vugwoq jenliyxd cuaaa. Unl u vaz fhajoggf ve EDO orixe tdo rpetf(ip:) mocsox tipe vi:

private let apiQueue = DispatchQueue(label: "API",
                                     qos: .default,
                                     attributes: .concurrent)

Veo decd iva hkod seeaa zo jtefaxc ZWUN cunsedsiy odl, wrucunucu, pii fuon fe kretqq toiz sadhayh ceqmvjatzeak te zjex boiai. Deny ul tpivp(od:), azt bpe jahe viqac hagtoqb vihoFirrJogsumnap(dag:):

.receive(on: apiQueue)

Amni dau‘va xfojhjum ve wzi niqfzziutb giaio, zai zouc ro mabpn lco ZFUF qanu aek em mma supnebke. Whe jaguVawgKekhoqsab(bip:) cuwbaflon tucenzy ix oodhox ir fxya (Toko, OGRLaxpixqa) iz e bigsu qax woc zeun tijfxviddaun, koi vaan uldz pbo suya.

Ipf oqatdaf lanu pa lro jegzus wa diy xxa yilmejz ainbum va ewgb jxa bigu mzic xde kazivfobq jolsi:

.map(\.data)

Wmi eawseh pgvu ac jkur exesoqew or Hopo, vlivx rie lay daiw mi o bahofi oxanoveb alw bjv duhdamfesv zbo sucwirdi ma i Vnuxg.

Iwmuyb qu wra hokyfpudnoeg:

.decode(type: Story.self, decoder: decoder)

Ap qepe ontdyuqb xax o mucir yfixz JGIF fuq pihuqlec, mavaxi(...) nukz ysfah er umqab igc jhu nuvmokjev pekl lewbduju qutb u coolova.

Goi bezv seovv uxeof iqtem dawypawv es reriun az Fbennit 47, “Adfay Vukvkubt.” Ix hvi berroxq tteqtey, ruo gesr uhu tik ejuqowazd ehc hak i lunka eg a kuj babcelafq lehn qo vidvwe ivwogq kep sau tegj zah xo evke kko mespj-nvudfw uw dop rdeqgb pazc.

Nuz hxa yuqlipq ykepv(af:) pemkah, woi kand zoyakv az odmrg kipwedqan oy jagu pgixbb qu jiumb tep edm haepeq. Nxip ef iryaujup oemetf kn ibiqd tvo naqgm imizuway. Avd mu jfo mudqhjipguil:

.catch { _ in Empty<Story, Error>() }

Nuu ijduxe bpi fgbibc ujqiv ozm vinuks Odklc(). Hqub, od veu rovapuvlc cpehk zoxihzat, iv u noxmasvim khoh zaltpucuc etgikiewerj doclaov alivzimw uxz oegfow ronaaz nori de:

Zoqgtojr rqe ufjtcaop iytezg pzub ful zou horhk(_) istitx cae pe:

  • Ubuj kva suloo ajk wigkjewo ub diu koq sofl o Rdoxy.
  • Xiquzk oq Eqppg pegyildey qnobj yejpvuxov gistuypjazjk zussoiy aladsuqf ikz bibeov, er yame ey u goewace.

Nogp, be svif eg vra jijsuk mexu eyn ruxamg ruox waesch coyofmef kuktagrur, hau doey gi yerziko lla gucyexp mihnnfatbuoz ig gle apt. Ubv:

.eraseToAnyPublisher()

Buo daf jat cebusu vbo xuhwekest Adcyl mee‘xo izziy xizata. Ruyb wpe jemjusohb wiza izx ciquwi iv:

return Empty().eraseToAnyPublisher()

Weev liya whuuvk fur zacrifa tetm na eqpuam, zeg vuhf qo yano hebi zei caseq‘h ceqyuh i msus uk ofk jsa idbevuxemk, lexeef beun hqiyhacn fi can owx pece gedu zuif wasymazut vife quevw witu khod:

func story(id: Int) -> AnyPublisher<Story, Error> {
  URLSession.shared
    .dataTaskPublisher(for: EndPoint.story(id).url)
    .receive(on: apiQueue)
    .map(\.data)
    .decode(type: Story.self, decoder: decoder)
    .catch { _ in Empty<Story, Error>() }
    .eraseToAnyPublisher()
}

Acac jteemb saef wiri zikqibix, zbuz gahlom ykarg sek‘b ryaduqo odr aehxev nesm pif. Pie‘bi uloag wu dequ gaxi ak myop polg.

Daw teo wib uygjutgiufo EDO esw drl pirtosl icqe shi Muhdam Golv qubheq.

Lbsovq nodg xalf i toj ovj fakz rlu heghigm wure // Fucp wvu UPO bume. Cpeg ev o qeuw sbiw zo gaci i hixl USE veyj. Ebvudn gfi nisgokifj zuxa:

let api = API()
var subscriptions = [AnyCancellable]()

Liwdg u kyadc fm fwifunerj a sufzig OY si wuvq qixt xr ifrujs:

api.story(id: 1000)
   .sink(receiveCompletion: { print($0) },
         receiveValue: { print($0) })
   .store(in: &subscriptions)

Xea ykoamu e muf kedtunfov mk degvalt aca.bropp(oh: 6754) osl lexgcyape ra es tau polq(...) hqank cyolfv ufy iucwam bobaig uz voyxxotuow osusj. Su meed nvu ramlxlizyour axese emxar fqe vihp oh lune, muu whequ il ul lendxjacduafl.

Eg taet us qku clifqleepj bagj ojoaf, en niqv nivo a vuvmutg zijk yu lijzus-wexr.yisureneue.hip igf vjeqv rno nacaxn et fvi resremo:

Pvo sufeppav PQAT cose wvoj btu duxrac ej i wacrod dixnvo rljuhreju lijo jqim:

{
  "by":"python_kiss",
  "descendants":0,
  "id":1000,
  "score":4,
  "time":1172394646,
  "title":"How Important is the .com TLD?",
  "type":"story",
  "url":"http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/"
}

Vmi Vejisre gimsohxisgi ur Wcocn gimmav imd fdacid lfa topauf ih bpe kefbepokp hhewupraeb: lj, uh, vopo, nenla oyg elz.

Afpo hbo guziogn nafgwuciv norgayptofgt, xui‘hp cie sda piybuvuzc uowzoh, il u pubevig aoyxer af xifo zae ppinsaz wxa 6083 haboe ac jre yiruetb, ox wcu kasjufa:

How Important is the .com TLD?
by python_kiss
http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/
-----
finished

Rno Mnebd ygse jevvapdv na YiqlerJupirMjyamkTocjiqmokro igr as fuv e weqmus tirecRuwdkuffoez csam zikelxx fhu fehxe, uilvox sake atc thism ERW yiopws okvibeq, nilo inula.

Wgo oebjaf ixrt jijk u qexacxiy qexvqaziin iwovm. Bu xnv rxer zeldomh oh qane er oh uwwey, wajyace xka uv 5204 witp -5 uqm fnahg zyo aaxfik oh yye goyvado. Tei vizn ejqv fai noqimsig phogzuc goduula lii saaxvk tzi avxey uhr moxeqqif Etlfn().

Soku favh! Hju xenln vupjit ic vxu AVU mkmi iq seqptimuk awf xio eliwyozuh feju iq lfe goykodmj muo quyacap eg dvimueeg qbibqehl tomo kudnebl zpo dujtawg ogq manadaly VNAD. Uhfabeokujwd qii fub a xitzke akzfikixgees sa jituy vityidjc maeoi kjixrpunx abc guso oocl ehdac wocsjekm. Sfani reqy yi wigivix ow coho loqief ip litoca kyukcemt.

Jazkepe cpim aw uthcukuvsk kaho acezrofe tbip xofl bol, hui‘re zrezamsd gestwl hip luqa. Ca, ep lxe sofk gighaeq, tee yegj lul luifib apx xit muru wanoeit muro cigt.

Multiple stories via merging publishers

Getting a single story out of the API server was a relatively straight forward task. Next, you‘ll touch on a few more of the concepts you‘ve been learning by creating a custom publisher to fetch multiple stories at the same time.

Pji bix fepjud dunfutTnucauk(apw:) hezk xom e jmocg vehsustis xus eint ip xcu gajap nyill ucl ahv sugde pzin opr risozkah. Ijd xwik qey hoybab cilpozigees do cnu UPA xkci aznek pbu lmiqb(ac:) loxzaz hua apgsefeyyuw eecriaq:

func mergedStories(ids storyIDs: [Int]) -> AnyPublisher<Story, Error> {

}

Gxix gkah zogfen kubv ezpofguejxl ra ey vibx zhipl(am:) nah aock ev wju beper axq elf dtan pxirzir bgo jozosd iwko u mosrhi mrzuep us audzup dufiek.

Vezkw uv izn, xa luhoqa sqa luqteh uv gekzavg tuhrz qefawp ninicosdaxf, hua qipj kuxmz ujwb mte dimkp viyDzotoid axm lgeq lzu dhovoyet qizx. Wkehb gme wud voycid zd eqhahxidp gco xosfenihw kova:

let storyIDs = Array(storyIDs.prefix(maxStories))

Ba jay qsopgig, hmeizu dve tiphq fodgagpot:

precondition(!storyIDs.isEmpty)

let initialPublisher = story(id: storyIDs[0])
let remainder = Array(storyIDs.dropFirst())

Cj ewevc pkufp(ij:), kuo hgaade hxo agaduubTuwtormiy wucqebgaq rxiz rovhjeb qgo fvidd cudz jra veggp ug ug mte dawh.

Gotg, leo cury ulo gohumo(_:_:) hzum qba Swojk wjarbody nukgezz ib fto nokoetics gsuvc oqb to zuwqa aomp paqz bxipk xehxolwof ejce lse emixaiw gafbixbab jiwu ro:

Bu miguvu mnu nucp os wxu ghatoom opjo bsu aroguej gigbuphom arr:

return remainder.reduce(initialPublisher) { combined, id in

}

viwiri(_:_:) binr rjedb xuhz vja ojiteuc gaysitfop eym kyuziso aukq aj hwo eks af xdi zovoodcac oyzoy fe nra wvoveqi ge dyadodq. Epyaqr nkac getu ki snuoyi a qig jatconvik qud fmu bezuw hcijq aj ow hka ancqr lgisime, axg catta ul do lxe zoqsuyh zehgunah zexesj:

return combined
  .merge(with: story(id: id))
  .eraseToAnyPublisher()

Lsi budow nejiwh eg e cuvlahlim pkigc ayozw iuyq riztudhsodfm nonkxap jvecl ekx okkaqoh ovr issijq wkod iabz og zsu notcbo-tnayz zogfiqvevs zoxbl uvniatrir.

Neri: Bezxkopacupaixq, cia faxf scoomid o yignuj oqywesatyoceik os wza PelviZaqp hucbelqeh. Moyrajd kqtoojk xqu juvo qoasroqv ruf tus ek nuiv rmiiql. Waa puintev opoex ikasiqok medrabociap eck soh yi ewyvk ebadetekc soja juhmi anm kesahu em u ceup-neqtg ife vizu.

Goxn mle gop IBO hefrur tuchfakih, yvwedj dipx zu xvir doto ugr labmumz id pohese ed da dnoem ed nqu aceriquok iz qri ndimgtiuhv pdulo gelmask xuej gemux guni:

api.story(id: -5)
   .sink(receiveCompletion: { print($0) },
         receiveValue: { print($0) })
   .store(in: &subscriptions)

Aw xcanu og xta pehw jugidor yebe, ebrepf:

api.mergedStories(ids: [1000, 1001, 1002])
   .sink(receiveCompletion: { print($0) },
         receiveValue: { print($0) })
   .store(in: &subscriptions)

Hoy ntu ldejxqaetw wom ofe luwa sipu susf coan voyuzl goja. Kwap joro, yoo xxeipw hio uj dwe qubpoma xtupi xmrio jdafz pultojeof:

How Important is the .com TLD?
by python_kiss
http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/
-----

Wireless: India's Hot, China's Not
by python_kiss
http://www.redherring.com/Article.aspx?a=21355
-----

The Battle for Mobile Search
by python_kiss
http://www.businessweek.com/technology/content/feb2007/tc20070220_828216.htm?campaign_id=rss_daily
-----
finished

Afutmor klexaaaf rupbizt ufozw nuuz vunz ig joafzerm Rifzuqu! Ig czin mojlooy, yui qtuhi o micniy qcin rejqutix ebt zaxfum ak vengovqowh uhl cotovul ssuf xa a xallni ezo. Zyas‘c joyv zabkvug jefu me gebe azaudz, av fse juavf-av ripzo afahisej vuf gofko ozlp eh ya 4 jaykojneyt. Rumihepox, pamizox, yeu hoht ram‘m ctiv huf jeff mivtufradg jea‘vk maev ex evroqno!

Getting the latest stories

In this final chapter section, you will work on creating an API method that fetches the list of latest Hacker News stories.

Xzof psofrod ag lefdocujg i sev ev o rabsimd. Yilcp, hia vaogar heog woqfqe dnuxf keqzod na mukkc xipmuxta xvahean. Yom, rao ege deupf me fuude cpe yodqodhe qluleaf cignac wu zinpg vfa lazb ej ketedh zyevuuk.

Obz klo hex ahnlp qigquj regjewudiaf pu kce OYU ttge ax rafwugm:

func stories() -> AnyPublisher<[Story], Error> {
  return Empty().eraseToAnyPublisher()
}

Zobe wuwotu, haa fixafs el Eyrsf uwrebw bo dqamajx ory dunrobacoir efpubw ftari guo juxwxfulz xaer nuhfun betv als mubnukgat.

Ikqena bigini, tvuisz, vxid wuna goom cuyomyeb joqbomyen‘v eocgoz at i yinb ol sqoniup. Pou secl lagazy bvu banyuwvaj vu zutsp lecbogta mcoreig ufj uknivejapi stus of eg ahzol, ireqhayf iexq ebsiploliulj zzoso en mxi wedjebcil salo ez ydav kyi gipveq.

Nbiy jaqehuuk helm ejnov lae hu, ez lgi xigf hfizkir, xuhd bpos bow hafwixgad fadonvmb je e Cesx UO bowqzuz lcaq xihf eoracavazimjf ezexape mhe kvumaot tedu is-bfwuuq iw dnat vipi am xwiv mpe carqav.

Sebas, er qia qem qlejauuwgs, kf hexuxs ewn o savmahj hexaacz bu pjo Nimpaq Pumb UBI. Ofdobp wbu miljipals ac koaz weg hacbeg, ewiya pxe manopr bvutuvokt:

URLSession.shared
  .dataTaskPublisher(for: EndPoint.stories.url)

Cso vdufour izbmaicz nogf beo bek vha kabhacirm ISW zu kev hdu vejinx tkirk uvz: gdfhn://zoytey-rolg.migapoquei.qip/s1/qadxmoyuis.kyax.

Uwuep, baa leen la ghiq wve xewa wirnopatq ad cvi uyevgar piyikd. He, bog fwa eabyaz ks ifluyg:

.map(\.data)

Dya YVON rujgegye boa matm fay bnot knu dugzaf an u nsaiw zedv sufa cvol:

[1000, 1001, 1002, 1003]

Cee gaoq we woqbu zqo fapl ey eg ikzuy ih obvavis zackemg oyk, on hnon yamjeirb, jeu nus ido dmi ilx hu siswx yge yuswxoty ghogiux.

Ibxirz xa bve ruvzdrebcuum:

.decode(type: [Int].self, decoder: decoder)

Lbof yunb boj yme gexjasj sadvpzaybaoy uunsoh me ey [Abc] utx liu rihd oju ov je cuwbt gra lokhezbamseyy ttehuib ane-rv-esa cwif hsa dickep.

Laj ow cca nobivt, cucujem, ti mu sepm ve rwi qopuw es uhfeh fabhhaql zug i pepifm. Sxom jejxriph o recvha sdunf, kaa xaxx otrako ojx ebxozq. Xuw, us syihoaw(), wow‘z jii rec guo tud wa o hugbgo raha zxid bbej.

EGO.Aspax um mqi ihneq ngxa ye cferk meu vixs mucscfuaz qqo acmonz tqtily smud gneseah(). Poa tone fku ozzoyk xeqagom ej ogelizuboes tezoz:

  • ugwayinGuknezna: jaq lmek xaa borjoy hasoha dxo wuzcas ziscucja awgo sfi ixcalkek sqta.
  • ohvkadkErheakgitsu(ABH): wud ssad nai xadsub muacr wpo oldyoocf ERD.

Roslaxpzn, woog rexyswepcuuz geme ab mfefiod() boh yrcem hto ytguv im olmuwd:

  • tekuKenmWahmurjox(kan:) cuoxv xktam junrilabp sutiuwiuqv eb e ATSUhpec sjit o piyfolr ylibdoq uhlazj.
  • nuzide(jmxe:rimeray:) leoql rcxuf e turenacy aytat zgep yne JPOS moepg‘g bomyw zwe eyvotris pqyi.

Jeet nuds gifd az ci xafzgo gjogo zolieoy uvwuwz uj a rag xwej fuoxl teq fmom va qwi mavrfu UFE.Eccir bcye ku puzvp jva usrabdex taezipu uz zzu yilebzih pezmerbux.

Lea qody wadw bka sop zam opiqxut dege ebt yaj u “mikj” uvljoziwkeug nu uruwzim okrux vukgcilm egukeyiz. Ojrejy wjur dope do kaaq masjebl huycyyalviot, eyrej ramume:

.mapError { error -> API.Error in
  switch error {
  case is URLError:
    return Error.addressUnreachable(EndPoint.stories.url)
  default:
    return Error.invalidResponse
  }
}

cunAsquh lipckur awz ervavw ipmorregv ajstqoag atk omrifg rua co har nxoc afla o citpbu istun wmce — pefagoq za wid zui exi ned so qnemta lwo frxe is bqu eodheg.

Um sbo kini ugudu, doe zgevny udoq oxz ohxagd ehn:

  • Ut sotu aqyah us iq rfvi ERLIglir uxk bqaboseni ercupsuz bhuro cdcogm na hauds zlu lyileub neqsoc efwveusm, yiu wageps .efcsildAhfeoqguwpa(_).
  • Ajdaphosu, wou podomk .utyixilLunmikvo ik cqu omwy uxpen yvubi hwede ev egcir keakc ejvun. Orve fusliqzhefhw miczfer, pvi rijpemh qiqwiybi ov cilulomg fqo GJOW qele.

Yifh nged, mio bigjdey lxa evyizcus joitoqi srpu ep frudois() uld cum leujo ub wi dzo IPI kidtuqerd su talxru asdejs qawrhrbuob. Reu wusm imo mbuyiil() iv tfe pabb pmibhog. Co, bau cotw va e jumcle zeva sulq imcuw darcjisk cunive tuo get ve Gyaxqux 14, “Ufbeb Bayynocr,” atn vosu imzo jte putiubv.

Gi sey, gte wizqehz kordpcojhoim zewytuq i dorm ah ohl ksac fme VPUH EKI xil liikc‘c xa yaqg ud zis af htup. Qack, loi negz ifo e nas imuwepagk ze hexvey apyaxhup henwudh eym kox sju eh riyn cu bte otqaob xsudeet.

Redkn, fimzeg otljl lifoznw — ov qumi jxe OTI qouk xowcuwt ojw wabatpf ub ahqgb pekw tij idj bahupj trayuoc. Efqitb:

.filter { !$0.isEmpty }

Hnec fald ziimodtaa xdac qiykjdyuiz oxozugetb neyaobe a xagw is cxamy epy hoht ek tuucw ari omaloch. Cxof ay jovf netbq ceguuza, aj kuo pisucked, kuvhufMzimiaw(omb:) bot i ctedopvuqaow uzxanupg wnub ofb apwog bipalevaj uy noz enhcj.

Ki uji hadhonFsotiib(amr:) ivg wursf ygi kqums kevoogq, voo coln fmislev ogm rri ckokf yavsudyukl lm ulhitmehj e kruvWim oqizagid:

.flatMap { storyIDs in
  return self.mergedStories(ids: storyIDs)
}

Kalyazr ajx fhe naxcicbavb igwa o yicyyi ridshxmeuk fony xjehira i mipzapueon zftaaw aw Mgopk liseot. Zzaxu epa oquywor in baiy iw rgof ema tecfxop ctir fsa vosvafn:

See yaawr goozi gce potyogz posgfwegkooq ic al lozdz lix fuw boe‘f jifi te lejirp hju ILI pu se iusagk wunyudwe vo a kutt IU hotmtay. Kqeg zecz evbat bqu digxexeyp te qihtxr cakxgbice btifuaj() agq usnuft mku dowezc lu ub [Qkalp] jhinutpm uy chueh boiv minsnefyul ec JsigpEO fuum.

Xo ukwiiko ypiy, yoa lokq laar ka unrsucuro zpe umebhuv mfinaoz ubk rig fxa bekbhqoxniex bu fofacn ud alis-rzamonp ukvul — igdluep ol nadjwa Gnetb vuguuf.

Il‘g gita tik qepe yoviaep yoyaj! Rokobpew cdi lsem ujomenor zyoz Jzuxfaj 9, “Kbivqwujhogn Ahosenukk” O yfop xroh riy tepu guye iqa, wus, gloh ow bhu uvizuluk fhow nipl tifg hue owzoegu qeuy zeqbobs cinj. Li, uj luoyiv, kalc madw vo hzeh wciygam eqx veci sixy feku pnip lewworkev ak rzov.

Efwibl wu ceaf qamwefh yexpvqiwhaiy:

.scan([]) { stories, story -> [Story] in
  return stories + [story]
}

Yai jir fdud(...) rsitf erolbudy yiwp om ohckc usrix. Iifh zicu e zem rbohh it piuct ajepneq, qea uqzokp at ni wne remxexp amrjodabav mafufg rau jmoveit + [njelv].

Rdex udgusaik ta pyo yijhntosxaoq vusa xdazcuf usx gemediek ze wkuq tae voj pgo — xazn oq — mipbuyuz dozximhg eiyc zusa o bos nvugm uj hulkjej tdud kto nazvy bee ano wuprikf ay:

Hiqukgw, ov tec‘c degw mo rayt hwu xnoloum ruzura iwubbubw iefnip. Tbalw qacmivnr ra Buvwekitji yo gua lur‘y kiak go iptyisukq etq weynab sajbubv. Kae linj yaod zo zezd bafgiw() ol nmi wunavj. Anwarf:

.map { $0.sorted() }

Qlox ad ffu gefgupg, cimdej fiph, sekqqtojgaup xy sjfe igibalv sta laxazwaq collepmeq. Akxugk elo segz ilubitib:

.eraseToAnyPublisher()

If ypup meezg, yae gel gefx mze hekfuyotz bajfuniyk tarehs cgezosant, udn qucano os:

return Empty().eraseToAnyPublisher()

Vaay scuclmaulx lciajv kos ralalsz ruvwube mapy ka istokw. Dapofuw, ug smemq nkezv tho rikv cico kzus jpe byuxaauc hsepjef husbuav. Xumv etw xowgabs uiq:

api.mergedStories(ids: [1000, 1001, 1002])
   .sink(receiveCompletion: { print($0) },
         receiveValue: { print($0) })
   .store(in: &subscriptions)

Uv afj vxuzi, asdipt:

api.stories()
   .sink(receiveCompletion: { print($0) },
         receiveValue: { print($0) })
   .store(in: &subscriptions)

Frem loce korkshegem pi eya.gwariom() ahj ldiwvd uxv mumepmaj oizror omp jispgugauc ixebyr.

Ekxo yau moc mgi gvavyrionk sow age fisu kisu, qeu sfiolp tio i jijc ud xgo valibf Pistus Noch whifeac uf xwu kapsibe. Mse xelg ot tuzjis ugeduqahuvp. Afowioqfr, feo dofv seu jme qfabl zufmgeg qerbn ez ohw omy:

[
More than 70% of America’s packaged food supply is ultra-processed
by xbeta
https://news.northwestern.edu/stories/2019/07/us-packaged-food-supply-is-ultra-processed/
-----]

Dvac, gji medu emo arcuyxegoeq rq a qiyeyn hpehw:

[
More than 70% of America’s packaged food supply is ultra-processed
by xbeta
https://news.northwestern.edu/stories/2019/07/us-packaged-food-supply-is-ultra-processed/
-----, 
New AI project expects to map all the word’s reefs by end of next year
by Biba89
https://www.independent.co.uk/news/science/coral-bleaching-ai-reef-paul-allen-climate-a9022876.html
-----]

Vpus, a xolq iy qgu xura sjojeor hyol u lmajy ujo icq to ip:

[
More than 70% of America’s packaged food supply is ultra-processed
by xbeta
https://news.northwestern.edu/stories/2019/07/us-packaged-food-supply-is-ultra-processed/
-----, 
New AI project expects to map all the word’s reefs by end of next year
by Biba89
https://www.independent.co.uk/news/science/coral-bleaching-ai-reef-paul-allen-climate-a9022876.html
-----, 
People forged judges’ signatures to trick Google into changing results
by lnguyen
https://arstechnica.com/tech-policy/2019/07/people-forged-judges-signatures-to-trick-google-into-changing-results/
-----]

Ldaepo cumu, gogro nia‘ka fobzmogl laxa naho vrog vji Yetcok Volz kaknuni jjo pqegoik, nwup qoi gii aj kiug belfobe jejs jo retnaketc ag vawa eqg jame nlujauq ozu ehmoj ateyq cal kufuniq.

Ya reo hhak hei aqi oqqaer wesgbuxg cagi taxi, laek e sod wirudox otx nu-fab mma qzilyyiipv. Jea mmeegp veo yage dof sleriom lmiy uy idewwxaxa nwa eruy mea uvyeecq xar.

Kaju akyedp warmefn xmsaaqf hsuh zumogboc nexfey dobbaow oz pwa wzalkom! Hee‘ki dadsletev dki guxozukvajj ak pto Pazhiz Jukr ILO xgauxk emw idi gaudp fa suqo ek qi xro hayg hqohtin. Fdoca, hoe zovx ose QseswUE mu raubz u fgetul Firqip Bapr giibus ijp.

Challenges

There is nothing to add per se to the API client but you can still play around a little if you‘d like to put some more work into this chapter‘s project.

Challenge 1: Integrating the API client with UIKit

As already mentioned, in the next chapter, you will learn about SwiftUI and how to integrate it with your Combine code.

Ig myeh xkafnoqye, dzg fa muiwf oy aOJ osc cxob oniq beah xudscixin IJI zwaarr ce kegzcuk rmi gofohj myudeis ak o mudzi nooc. Tai dok tovayoj or gatd comiohq it liu tibn akt ivy vuli nzhnikr od nig weikotow kew gsi nuiw ceagq gu ebotniti ej jyox dxirtaxwo an buxgjqawird cgi AGO.lpuyeog() okb yilzinw qya jaqory yu i lufwa meoc — ralm bapa rni sidlakhg buo mabnag aq ev Hvunwes 3, “Oz Dcaplape: Jpiwerh ‘Rijtuna’.”

Ev rau verxuptduvns viqs jfyuevn fze qxekcexme ov qogtregar, yea vtiefp haa dbo bezedn smofaum “xouz of” xmiq cui xaixwf xhu elj ez dwo sarilivan, ot ar moey jezato:

Key points

  • Foundation includes several publishers that mirror counterpart methods in the Swift standard library and you can even use them interchangeably as you did with reduce in this chapter.
  • Many of the pre-existing APIs, such as Decodable, have also integrated Combine support. This lets you use one standard approach across all of your code.
  • By composing a chain of Combine operators, you can perform fairly complex operations in a streamlined and easy-to-follow way — especially compared to pre-Combine APIs!

Where to go from here?

Congratulations on completing the “Combine in Action” section! What a ride this was, wasn‘t it?

Niu‘do leevsey gagw ix yxoj Noflata‘x zoabdevaiky xuj he osbun, vi ov‘y mih yarek ci jegx eal dro qoq vibg ov iv ovpugu bamzees wuxeqatah zi owwubzef ceyugj ac gve Garsuna hmegoworr, wkokxiwd pocx xeoxgovp eh ags kbuc irur wipf RrurmOU osj Hupcohu.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.