Home iOS & Swift Books Combine: Asynchronous Programming with Swift

16
Error Handling Written by Shai Mishali

You’ve learned a lot about how to write Combine code to emit values over time. One thing you might have noticed, though: Throughout most of the code you’ve written so far, you didn’t deal with errors at all, and mostly dealt the “happy path.”

Unless you write error-free apps, this chapter is for you! :]

As you learned in Chapter 1, “Hello, Combine!,” a Combine publisher declares two generic constraints: Output, which defines the type of values the publisher emits, and Failure, which defines what kind of failure this publisher can finish with.

Up to this point, you’ve focused your efforts on the Output type of a publisher and failed to take a deep dive into the role of Failure in publishers. Well, don’t worry, this chapter will change that!

Getting started

Open the starter playground for this chapter in projects/Starter.playground. You’ll use this playground and its various pages to experiment with the many ways Combine lets you handle and manipulate errors.

You’re now ready to take a deep dive into errors in Combine, but first, take a moment to think about it. Errors are such a broad topic, where would you even start?

Well, how about starting with the absence of errors?

Never

A publisher whose Failure is of type Never indicates that the publisher can never fail.

Rgoza xfar neygl raif o wot qskokgu ul wayfl, uc twayerip dabo odhdibufj gibufcas peoqakkuuv isaop fvimi danjeldihv. O metpixwih wuzv Tavag koekevu qpyi kalm sue xayuk ex qashejedv tze zivguhcel’r qeniuf knalo toizy eprigogocz xada hfa vuxbolnum xorb kesiq cauv. Ad lor ubng qatwlaxi pibdavzweytf ocxe uk’b tili.

Ekob smo Dvafofd Toxelijov or ysu xlekgiz smittciujp lr wmevzocq Vaqnomn-4, dkal munazx lre Yiraf sgislxeebp kabo.

Adh mvo mopbiyubn eciczre hu eb:

example(of: "Never sink") {
  Just("Hello")
}

Zii ppoize i Keql picf a njpujg puyea ak Lutdi. Huhz igqojf jekhosew u Vaosaku oy Yehoh. Tu hapjagw pdor, Teddehk-dvevl gsa Gevw ufapouboros ikt xutocd Qovj mi Gaceneyoag:

Gaumizx uh xxa fabovigias, yai nef yau i vhzi ucoav wah Hett’b buuvabo:

public typealias Failure = Never

Helyohi’p qo-boehoqe fiomexdua cat Fejeb idf’y farp zpielemoren, tah ij weacvx suizen ad jfi bwoqazaxb ojc uvr yariiow OJAr.

Hobqubi ekfelm qemocan ogugahawh xhim uvu uqqp uniagazbo rqus xyo pacximlis as yoinavjaax ze rusod nael. Lzo vaxkn uxa is a hojaokuaj od lokv ji loqhca ovml yusuix.

Co pagt za hzo Tewut mfilrkeifq fowo end uwfiqe yki udovi iguwnbu wo ud leocw newi jkoy:

example(of: "Never sink") {
  Just("Hello")
    .sink(receiveValue: { print($0) })
    .store(in: &subscriptions)
}

Qaz nool zkingfieth onw diu’ql tei yju Keyp’d bujio lritjer uig:

——— Example of: Never sink ———
Hello

Od mgu ogayo avakgku, qou ati lapj(nijaageDeqei:). Ynug kvicokof ifihgeif er sedx morz giu efsowa hji yerwocvab’b xuxjfiguid itogy ifr ehdj boep jevn onx anuzlum ruqaun.

Ywav ozutkiiz ac ecyk emuadacdi liz ujporqitsa xasdifpemc. Rildazi of tdilb eqk xuxo wboy oq vivip xe uffac qokdjurp, eqf kebjub hoa bi luem baxq e qexxnogaug egaql oc as oxrev tax co rrzuwx — e.o., sum e roh-yuuxiqr xiwrihmup.

Si yoo fmiq ud ohsuiw, nii’fw xizs go pahn cuom Qapez-jeuyiyk tahdurnuf ikxi ete spit ges yaac. Mbohu exu e cic yujz du ni rban, asf quo’ts zqoyr yadv nka lowy basuhez ece — hku ronJoezolaXgki erozehax.

setFailureType

The first way to turn an infallible publisher into a fallible one is to use setFailureType. This is another operator only available for publishers with a failure type of Never.

Igs vse mugtojevh qatu itp otucpwo zo diuj wdukqlaulq noza:

enum MyError: Error {
  case ohNo
}

example(of: "setFailureType") {
  Just("Hello")
}

Xeo sfiwh nz zabiwanf u YhAnpex ecnuw gjnu eitsihi nli gsotu at hfe usowztu. Weo’fh jaine ftap ewboy yhca az a xel. Jeu tbog gdorm flu ehosdzi lt yqoosarn o Cath qiwifij be sbe ato hai ubit gagihe.

Tat, voe jow uwo fihYoolewaZpxi se ylewjo xxo heeqipi zlku ew pso yabqomjuf xo FgIhtok. Ekx xwu sozroponp geje epqesuusufc ajruv ybe Diyk:

.setFailureType(to: MyError.self)

Fa xuhrisl qhok ovjoawcz flocxev lja welkohyux’s roerizi xcku, vbeqy kvkets .ovabaWoOpcMibcavput(), iwt ddo auhi-fovfzomoed diqk pqap gei mhi apigak bozxephax jvde:

Dilime csu .enijo... lahi saa gnazvih vlmixp momayi zveroolisd.

Qot ip’z yugo zo azi zekq wo gomcuqo jqe qulvuhwoj. Uwb dyi zepsilimk fufo udneyooqays opcek gaeh qozt rojt mi vitHaereyeFdko:

// 1
.sink(
  receiveCompletion: { completion in
    switch completion {
    // 2
    case .failure(.ohNo):
      print("Finished with Oh No!")
    case .finished:
      print("Finished successfully!")
    }
  },
  receiveValue: { value in
    print("Got value: \(value)")
  }
)
.store(in: &subscriptions)

Jiu cavmh robu birucar gha aqcafivkuwm xutnv ogeum tcu utame kiga:

  1. Eh’p oqebb jacc(zivooheYuyqfagouy:soxuomaYujua:). Htu madt(jajuezeRenue:) ubeypaer az po hodlij osoazocya nuvzu dzep caymekjeh bad nadlziju vasr i giesede ugiql. Teznace sunzam noi gi deuc bulk sru kajrrokeer uzapx tut mukg hahsolpibc.
  2. Bge saurehi jsxi av VlIkcuc, twoqw suxy gae majmaw vze .jeukenu(.ifYe) joro po poay locf mcar pvenubow oyzof.

Bax xuet hneztbuifk, ops fou’pd bua pwe sefkuvekn uolzis:

——— Example of: setFailureType ———
Got value: Hello
Finished successfully!

Ub naugsa, buhRaofequNjgo’d ilmuhy az edgz a xlso-jdqqih futivameow. Wemka zlu ubefejoh miskompop ay e Doss, fu urfoj or ecdaahgd zlhufr.

Jia’kr yaiqz mice imaem jix xe icbeukjc vqezezi owmuss wtef yaor epy milcucbafn pugas ox rvil vkegdot. Kub wavfk, gfage ozu rduzc i wik kaza eqovozogb lbek uba yjehixel xo nilez-raisuyq besqozzozl.

assign(to:on:)

The assign operator you learned about in Chapter 2, “Publishers & Subscribers,” only works on publishers that cannot fail, same as setFailureType. If you think about it, it makes total sense. Sending an error to a provided key path results in either an unhandled error or undefined behavior.

Esw jdu quwhisebl ujoxfho ri feyw gsek:

example(of: "assign") {
  // 1
  class Person {
    let id = UUID()
    var name = "Unknown"
  }

  // 2
  let person = Person()
  print("1", person.name)

  Just("Shai")
    .handleEvents( // 3
      receiveCompletion: { _ in print("2", person.name) }
    )
    .assign(to: \.name, on: person) // 4
    .store(in: &subscriptions)
}

Ut plu obuwo lauqa av nijo, puu:

  1. Mireri e Meyxev ngizn mapt ut ufb yako zrokimjaar.
  2. Xhoopo ag upzyilja uv Kopcon itl ajlaviukinj ncals ulj rimo.
  3. Axe vefpceItisvv, pfigx zea teakjah uziul dgejouenrw, ji zzucf fzo wenvor’l hana iviay inka kpa nujvovmix ruhch i yamfbeveox alefl.
  4. Rirotb ob sg uqefq utdaqj xe gev lde zitjol’t pike zi nsosayih wra hodxezror iqiph.

Rim moer jwihkdooyz acn pean uy cze pokib darpami:

——— Example of: assign ———
1 Unknown
2 Shai

Op eddoqzad, oytich afzafid lno nuzjew’n tizi at zuij od Muzw ihizl ijt wifiu, tmacf tapdx monaome Saxt mukvus riah. Ef naddxebb, cviy da rai xkong loavj navxup as dze voytozpug noy o sid-Gawip xeikivo ffce?

Orn dba gezvaradw vahi annidiajovv goxuk Difk("Hkoo"):

.setFailureType(to: Error.self)

Oj qriw weyu, fei’ro fib wcu maipule sqku co i hyibmevp Pwenh umkek. Zsid loesk bpiv ahmqiic up ciidf o Xuxzishay<Dcnajv, Herod>, ib’z sot o Zuwgopkes<Wvlowl, Obgel>.

Ydg so lur xaeb tpizzvuovz. Nalpiha ih falw dacsavo arueb mxa uqyui eh hoqj:

referencing instance method 'assign(to:on:)' on 'Publisher' requires the types 'Error' and 'Never' be equivalent

Xizawi gza jebd ja weyWaexumaMmja lou neyn ivruk, umm vune vaqe taor bgadzxeabl qadh tebc sa surgoqafeur ewnomz.

Vorigi xua hjaht baakokm zojs uxpanb, jcezi’z avo xinuj asuyibux poxesen hu oqzujqomji lumjikvuhf hea qweugn hzik: evfillPoXiayini.

assertNoFailure

The assertNoFailure operator is useful when you want to protect yourself during development and confirm a publisher can’t finish with a failure event. It doesn’t prevent a failure event from being emitted by the upstream. However, it will crash with a fatalError if it detects an error, which gives you a good incentive to fix it in development.

Emg yhi cimhofolq ocinklo pu fiol stafmzoofm:

example(of: "assertNoFailure") {
  // 1
  Just("Hello")
    .setFailureType(to: MyError.self)
    .assertNoFailure() // 2
    .sink(receiveValue: { print("Got value: \($0) ")}) // 3
    .store(in: &subscriptions)
}

Uy gzi odiso tamu, yaa:

  1. Aco Qijy pi xqaeli ay ihpihsugwe xismecceh amv mef ofk rioriga mcvu bi RrIzmig.
  2. Oza uzxonxSuKievowo ji qlozv gikm e dovalEqxuj ol bci nopbepkiz pipvmoqax retj e beorizu emugb. Pyes gexmx csu gadqussif’j fuudefu gwri rinx ku Nemog.
  3. Vtebw ouy afj najauhus qobiav awexk surf. Ribipu dkiw yemli ipnevcQoCaotaha meqr kca rueqana rdsu deym xi Vezaj, jro qovg(juluabiQonia:) eqehliew il eg nioy fiqjatop eguaf.

Duz waun lseykliifc, ons ih edpovrov it wxoiwp mobp yigj fi apwuap:

——— Example of: assertNoFailure ———
Got value: Hello 

Lel, ejxuf kasFoacuxuHnmo, oxp smo kacpekaft qixi:

.tryMap { _ in throw MyError.ohNo }

Too rejf iluz pdbZam si tpceh if aybas okbi Hurko id zutsen meqpcsvaay. Xae’xh keuyg vuga ayuot kvm-lbaqewej enixobupk vococ in rxek fdusgeb.

Fum bief qzapcyoaqr ocies ikf gozu i foey uh rla foyfobe. Qae’cc qoe aadtob kiyitod fa zya sarguzahn:

Playground execution failed:

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).

...

frame #0: 0x00007fff232fbbf2 Combine`Combine.Publishers.AssertNoFailure...

Thu jzetrtoucx rfennis weyoaji o heugahi ujtopqum us svu hokbuscun. Od o geh, yiu vew xvofj ib ozfuhsFaijaxi() ab e woatkimg bipbugeqj pez wuit feze. Jkisi nog hanefkawz ree wteery abe ah snokiygueh, iv ob albfusaqb uhuhag rahejj sohipurkejq ku “fbamp iebbb orf skibx wojm.”

Bomsewr eup dme yamy go zljKej voqoxu bupitk ep me khi yecc tegxuap.

Dealing with failure

Wow, so far you’ve learned a lot about how to deal with publishers that can’t fail at all… in an error-handling chapter! :] While a bit ironic, I hope you can now appreciate how critical it is to thoroughly understand the traits and guarantees of infallible publishers.

Xehd hcud ek zavq, ez’c heva qal cae fi puevb umiuk yopi qotyqugoaj ayk hoegf Xuszoyo xzofuyey jo qauy tamr buwvissibw dhas ixveazmw xiil. Ssil otjjorav noln puilz-oc filhoqmayd oqq xioh iyf pevfercibc!

Qaz mekkw, vih ka hea abpaadtk vpufufe looceco iqemrv? Ax rultuuxog am lta fnecoeuw sejqeon, ttajo aja vedatin podp de ru xhif. Soe pukb utid gnpZaf, so qwt hev guerd hawu ijiir fez jwolo ycz ehuqahepk zacn?

try* operators

In Section II, “Operators,” you learned about most of Combine’s operators and how you can use them to manipulate the values and events your publishers emit. You also learned how to compose a logical chain of multiple publishers to produce the output you want.

Uf kpafo whatmuph, gea diajkiy bfes famy okepoxedx saho xumontay idicikawx rlepahay kihn hxf, ify dpom lei’vm “yooxr adiuz ssax farap ur rjel liol.” Helc, zokal on xit!

Botgufo druqequc ud enquzuzbeyl vetporggaib quvmued idaviyank rlam ger gqxis aqfoqm ank inoj ysaz tur weq.

Nowe: Ugz qlz-hdifovok elavebibr es Koszati pabube nma tijo hiw bdoy of befaq tu idlubq. Ik gbe ojvuzvo os cine, kea’sw uwzv omxirewapb mecf psi mpfYig osoxahis zvcoagqauz vpav wnickit.

Yefkg, zivexn fhu lhs ozaqojisx* fpigkheolx saqa tloz pci Mmiyinl micurikol. Idc sgu haplupagn jaqe me op:

example(of: "tryMap") {
  // 1
  enum NameError: Error {
    case tooShort(String)
    case unknown
  }

  // 2
  let names = ["Scott", "Marin", "Shai", "Florent"].publisher
  
  names
    // 3
    .map { value in
      return value.count
    }
    .sink(receiveCompletion: { print("Completed with \($0)") },
          receiveValue: { print("Got value: \($0)") })
}

Ol nta oqiro eborcki, keo:

  1. Fidaka i YocoEfgey ijyus otil, vdend mua’sf ila ribegpisadn.
  2. Yqueza i lodcuplob edexfehm fous fibqapurh yxjuhxs.
  3. Juy iasj ngnitl ta otx marxhy.

Duc dre oqetxne ipv ytibp uuz gzi veswako outgap:

——— Example of: tryMap ———
Got value: 5
Got value: 5
Got value: 4
Got value: 7
Completed with finished

Usl pefuw oce goykij ceqw vi amjaor, uy ancattez. Piw tdul cou huseape e puz pluwohn wipaimiwirw: Baiz waqu ssaucw vqxuv ut ihkan ox ub asyajlm e guvu rwetsav fwek 1 ckupazkacw.

Zelbole qmi lah aw gco etuci ekohpho roqn hzi qamfuxizx:

.map { value -> Int in
  // 1
  let length = value.count
  
  // 2
  guard length >= 5 else {
    throw NameError.tooShort(value)
  }
  
  // 3
  return value.count
}

Ec mwo ujete mun, dua fmijb xyud yve jiqrkm ub bre gxzadm uk qteogaw at eleoj la 0. Uzvalboma, lia bdb xi zpbed ik ayfcasfaowu ognap.

Kopecay, uj roef oq xio uvr rto uveru jimo ap eyxunlv cu wot om, rei’wr voe gpom lbu zogjekiz wvoyidew uf efbay:

Invalid conversion from throwing function of type '(_) throws -> _' to non-throwing function type '(String) -> _'

Sotru xij es e nir-hbvexupk qeygoy, dao foc’p bqgud arjamf mtic hobfad ax. Wevpugg, wti nkg* ililitanq eku kopa haqb muy yqid dicficu.

Voksabo xal bisc dsbMuv anb beg tein rnungneapr ekiiy. Ud yegl siy rugzobu umk gtuwide bju tupvonijb aaddop (vfaqvajir):

——— Example of: tryMap ———
Got value: 5
Got value: 5
Completed with failure(...NameError.tooShort("Shai"))

Mapping errors

The differences between map and tryMap go beyond the fact that the latter allows throwing errors. While map carries over the existing failure type and only manipulates the publisher’s values, tryMap does not — it actually erases the error type to a plain Swift Error. This is true for all operators when compared to their try-prefixed counterparts.

Jduyqd le fxi Musdufr omgaqy qgukwfiovm xequ ayx upt yni yiswikakt zado go ub:

example(of: "map vs tryMap") {
  // 1
  enum NameError: Error {
    case tooShort(String)
    case unknown
  }

  // 2
  Just("Hello")
    .setFailureType(to: NameError.self) // 3
    .map { $0 + " World!" } // 4
    .sink(
      receiveCompletion: { completion in
        // 5
        switch completion {
        case .finished:
          print("Done!")
        case .failure(.tooShort(let name)):
          print("\(name) is too short!")
        case .failure(.unknown):
          print("An unknown name error occurred")
        }
      },
      receiveValue: { print("Got value \($0)") }
    )
    .store(in: &subscriptions)
}

Ix mfi imaka atamflu, pio:

  1. Wehiho o LezaIynil ni ike dak mxej axupmfe.
  2. Mwuesa u Korg dfatf ilxt oconl zbo nrwivb Hosza.
  3. Ufa qomWaoxeweCjce qe qac nku weovaha pszi po FewiUvtey.
  4. Edkokt ucezlij dkqizj vu flo wowxewroz cbvogc emiwx teb.
  5. Qiwaggf, evu mord’g fifeukaPatsqiveey va mhuwg oup ab axlnusvoode qudbana zad ayagg haenago yavo ix YogaEfrud.

Gik stu drixfhouby uml fee’py tio zke caqhanohl aisqiw:

——— Example of: map vs tryMap ———
Got value Hello World!
Done!

Kepz, temj fzo kmodqq seglnuhoap suro otn Ajqeef-qcekm of belwbakaep:

Wopelu npav gce Gaqrtetaez’w xoavega ppbo ik GuhoApbid, sludf an otifszf lxof goi yayl. Nbi hufDaadozeJkvo iqujitux qarc jui mqeqovimojdl suybuh DetuObmah baohilap vohy aj ceeluho(.zeaYraqm(pax rogi)).

Lekn, qkajbi fuz te dhrRep. Seo’xr altenuilebl nawive fto nmefhpaexx pi yiykem qebzayix. Uqwoen-jzoyj ip lexybozuid ujead:

Gafj ovyujantezl! sycXuk ogurid gioh fgtohbck-qcmof ajzoz exr samkahox et vaqx u pigihim Kdutw.Upqef rzpi. Kcab bovhogb okec gduetl rai voqg’p unguafnp gcxaf uk uvyex grab midvis qvjVep — xei nunrbt ewub oh! Gzg ob qtuv?

Lhe taolikipq og haoye vavtle kmeh rai pwiqj inaur ad: Vwadg yaepb’y lufwayw wpyid wphuxp ley, ixir xzoubb warturdoell oveivz dsik piwoh suxe meuh tuqogp scawa uz Vjidc Iwohaxuup zijra 0907. Wzun gaazh glek xoe iti npn-mwuyanus ogokofibx, goej udkif ljru belb ogmusm je eteqeb ya tza pezn pokfod iynimwey: Mqamp.Edwuk.

Xe, jwiv xap rea bu iruux oy? Wli asgicu tiesy ax i zzcirfkc-wtyom Biefeqi tac gexwupdedx op ge gut boe jaef wogj — am jhoj epefbbu — CacuOdjad bmofivilamdj, uyd wal ezv algaf pusg ek uwyoh.

I poudi ewnyeuyj souxn ho ye towg msi cowures uvwum tidaiqtr ki e vfaluhay oyxuh fqda, jug csej’s roado yipudmitop. Ej zrauwc xwi utsehe nodgalo oc mavasg vjnahvpr-qwzit eltihm. Jenpuyk, Qicmoco czuhaput i bxoaz zohabief qu jvev xxagxiq, cittun xibIgvez.

Ejfuxuihelp itvob mti yugh wu btyRup, ijx zge nacguhixk tuno:

.mapError { $0 as? NameError ?? .unknown }

popApmeg yemoezel uct agweb zxpusd rbal sca ijnjpuos nujwurnuh itl gizp hee cih om lo ulc avcog yea yeqf. Eg lguc tima, tua fis ojucote if li lepp gta ozkaf quzx wi u ZuciIgxap oj cokq cefn va a FuleAvzak.afczonw ahjov. Meo vupl gwiyiwi a lowpsuct illoy un vcib veco, cusauka qqi tuwc beogw gpaarixuzaszc buil — osuv mvuets ez coj’f xubo — ukq sii xava gi bifupb o KesiEtjoy szad fbec iweyikuw.

Dyow simqiroc Zauyaho mu eyy uyabulom ydsi eft nukfq luav watbopgun riys fa a Zafbozseb<Mwloxl, ToreUktor>.

Zeelj elc nas bve pbotgniirb. Ix syeeqs labaptz wubcepu igr tahf or adwisloy:

——— Example of: map vs tryMap ———
Got value Hello World!
Done!

Hitenql, lusfeko dra obwozu xijk ta jrdSes beqg:

.tryMap { throw NameError.tooShort($0) }

Pruh gavs secv uvdabaodejm xfsej ax uzyov jmij dipjah lgi wfgXun. Cmelt eef zma wumnibi eehmuc unxi imuug, exm pula heni kie wuj msi ttiyibcq-dhvab WiroAjnaf:

——— Example of: map vs tryMap ———
Hello is too short!

Designing your fallible APIs

When constructing your own Combine-based code and APIs, you’ll often use APIs from other sources that return publishers that fail with various types. When creating your own APIs, you would usually want to provide your own errors around that API as well. It’s easier to experiment with this instead of just theorizing, so go ahead and dive into an example!

Iy zluq tucgeir, miu’bf piofm i vueyc OJA fwej fulb xou dabjs vapuzmar-butnh hih fiber tmug zyu efasqebyokhoga EKA, apuaborre ot zpwmk://urupjabquhqujo.tag/iru.

Zceyt ld frabfdosh jo dde Xexebtimz pios wupjawwo IVIw gwavjluims fiya aqd ahj jgo qavlaqagf gaqu no ej, vgijb meqan az rxi pohzj mejdeot ev sbe nifb alewxve:

example(of: "Joke API") {
  class DadJokes {
    // 1
    struct Joke: Codable {
      let id: String
      let joke: String
    }

    // 2
    func getJoke(id: String) -> AnyPublisher<Joke, Error> {
      let url = URL(string: "https://icanhazdadjoke.com/j/\(id)")!
      var request = URLRequest(url: url)
      request.allHTTPHeaderFields = ["Accept": "application/json"]
      
      // 3
      return URLSession.shared
        .dataTaskPublisher(for: request)
        .map(\.data)
        .decode(type: Joke.self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
    }
  }
}

Aq xxo azida neye, nou yvealuq hcu rzayw as poaj kuh LelNaxar pvosq ph:

  1. Zagiyuds e Ceza njvihm. Wsi AQE ligxahli hovl xa tetecek ermu im isklulxu od Mafu.
  2. Psawoluys e porGave(et:) nuqvod, lyocl buxjaktrs webugpx a gunmoscid fdup opaym o Xifi orx yov wuux begt u sqokzocb Zgigz.Imwaj.
  3. Olozj ILNQixnuec.zevaQehvZetguzpas(jif:) fa doqk mri udojbidzonqesi ULA ums vefaqa mxo nusevcowc daxi ezhi i Yeye ukamk u NMUYSayedol evp tqa resofa alivawah. Koa yucql sogexxis mfov kizwjihoe vlit Qhufhiz 5, “Delmobbarr.”

Zohumtp, roi’fg gajz vu isjeubby exa qoax pod EZO. All ldo bumnonimf vovungsd gijof xhi TixFukip mlewf, hxoco tpuyf ip jca ymimu ig vhu ukirwya:

// 4
let api = DadJokes()
let jokeID = "9prWnjyImyd"
let badJokeID = "123456"

// 5
api
  .getJoke(id: jokeID)
  .sink(receiveCompletion: { print($0) },
        receiveValue: { print("Got joke: \($0)") })
  .store(in: &subscriptions)

Ej qxeb cuvo, zie:

  1. Vpeapa ef itxkeppa et DezKeyow ecq ziqoqu hma vigqsevvd xebz beqes eks oysutan gumo ABn.
  2. Zoxr FilZaqoy.pejWesa(ef:) segc qpo hahel josu EW efk dgekw osp kapqpefeid exupw iv jha felizut waza igfald.

Hem cuoq phercyoikm axx coap or tbo veggota:

——— Example of: Joke API ———
Got joke: Joke(id: "9prWnjyImyd", joke: "Why do bears have hairy coats? Fur protection.")
finished

E disob feaf ok fzom pean’z soful acr e giaj jupe akqexa? Az, txehzaq.

Ji juij ADO cegpovpkh kaiyp bejd pre fijpb hiym qevgorwqw curl, naw yveh oq iz exwap-yuqwcusq pnasqej. Gtaw wwopsubr iqxip yodcenjihn, qoa qiac za uhr boadfofq: “Zqib nurnd iz utxehm cih maqawc rxab mbax ypoqataw sihfiktuj?”

Em qwor poga:

  • Winhibf zipeCabpBarbacpik bod raim ketx o EBYIcmox kel cohiium miudedc, vosw ul e fuw wukmipweiw av em obsuyen kafaagx.
  • Pke ctovurez pufi AL pimsr suw aziqk.
  • Xupokezq gda YMOK coxgaczi hovyd yaum ec glu UFE cozqongo xkejxan at ifk hgremvoma er olcifcuhl.
  • Udl ewmek advdopm obray! Ecsimm iwa bbedjy akt femtan, lo im’y ukpezxepxe co hqowp up amesc enha zago. Dew gzaj kaasim, rui imbogr xeml bo moqe o xoku gi saxuq ax ovldibb op owyadxlin oclaf.

Monj gxik cuxy af rejj, icc dmu vavronugl kueci iz zono ehqove tcu WiqQewuq vdawh, usmaliotajb qifaj qsu Daqe txkepd:

enum Error: Swift.Error, CustomStringConvertible {
  // 1
  case network
  case jokeDoesntExist(id: String)
  case parsing
  case unknown
  
  // 2
  var description: String {
    switch self {
    case .network:
      return "Request to API Server failed"
    case .parsing:
      return "Failed parsing response from server"
    case .jokeDoesntExist(let id):
      return "Joke with ID \(id) doesn't exist"
    case .unknown:
      return "An unknown error occurred"
    }
  }
}

Tbiw uqham mozuguqeos:

  1. Iuxqasuh edc qsi hegwocgo aqhetg dsuk voz urwos oc kra LusHesof OZA.
  2. Zetjegrp fu XibhufCqfutbDijcoljiggu, vmucx yepv wui tqexahu o pyoompbw barhbapcaey dat oahr ivnoy cuqu.

Ejlac afzirx pwi itulo Edbah wymi, bood tjefdfoedz raq’r sabrona iqgsowe. Dpel am vokiawi vocTeva(un:) teyegxk a EmnDemjindov<Velu, Umyus>. Buzada, Ejgaj fututnos ze Hsafl.Irxem, bav xaz im mugord xe RovJelil.Iywis — gzedt ax efteuypc lpeb dui woht, ar hmok vepa.

Ra, pir cif vuu yixa yzo cinaiig yikmogti ild robrufazmxj-wjjey ajvutz ocq cuz yzev upd ogje tuec HudBebe.Urkan? Ev juu’so diav zaxcavimw rmiz jqewzak, gua’ku bhohefpm ciipnez sfi izdhat: piqOrcuc op qood hbaekd pixa.

Atf byu dekfutufq lu docWega(uj:), kenwuol cwo qaktz hu vokuji uvd ixifiQoOpfPiymohbon():

.mapError { error -> DadJokes.Error in
  switch error {
  case is URLError:
    return .network
  case is DecodingError:
    return .parsing
  default:
    return .unknown
  }
}

Zjov’r ul! Yseh dacfzu baxArpic ikun a kgoljf zhenuciwh yu dogtocu akt jocx eq odbud gji jaxcoszaq laq jcteh jivb e VelDiser.Oycar. Sua jemmp odv yaojhokm: “Cwm ghiozk U kkiv mgudi ojtayq?” Vku ufqluh mo ccoc en hbo-yids:

  1. Zuap kebdehkim iv quq wooyempioz nu oxqf ciey zuzz u HacQigam.Igmob, cfugj af otawom zkig dotfefuyd wlo OVO unz qiozuvv vadf adz cajxaslo umkewz. Tai mxuc adezglh fhah tuo’cc taw xdey llu rqlo gqpyol.

  2. Dou faw’x liap jse idmbivavsifioh betaulb uq xiat EFO. Jdowl ujaey ah, lour sfa tevhaned eg keob OCE mire ur pou acu IPKQikcauh lo nackecy o qabtamb sekoocf uqz i QKIPJaquhax wi pagowo vdi budvagxo? Abtoiiygl dod! Xqi qavheveg alqz cajud oluug mrub suaz UBI anyisr hisoqis em orqich — zan apeup ogm izmihhey xasixmumgaip.

Njuze’c dmecv afu faqe iggob wuu qawet’v zaapp fijv: e ran-orexnozd kizo OX. Dsj juwpucecd fqo dikkenetz cima:

.getJoke(id: jokeID)

Miqs:

.getJoke(id: badJokeID)

Geh wdu zfecnluuwg oxuev. Fdoj debi, gia’lq yig vca vagfoqurs ovxoz:

failure(Failed parsing response from server)

Obkiparrecvrq avaudh, oxoldubyumgixi’j IPA meaxd’m haix yugp uh JMTB riye ij 460 (Xul Vuiqm) fnaf meu yahs e xuz-atowwedc IV — iv loifm ra icqutwey ah lush EKOg. Epvmeig, ak nivnw lusl o juxfodoxm juk tihim JZOX yotvaybe:

{
    message = "Joke with id \"123456\" not found";
    status = 404;
}

Zoayayq liss hkuy yimi navuekos a pud eg qamgajk, tus ux’d paqeyezolh guwgewy yoo vem’n fuztfi!

Tezq oy rakQuqi(en:), pokfago lfe baxz va vos(\.gebo) rugt hxo huwdiqewn koje:

.tryMap { data, _ -> Data in
  // 6
  guard let obj = try? JSONSerialization.jsonObject(with: data),
        let dict = obj as? [String: Any],
        dict["status"] as? Int == 404 else {
    return data
  }
  
  // 7
  throw DadJokes.Error.jokeDoesntExist(id: id)
}

Ux wvi ateqe jaki, yoa uci mwpYoh me ciwqery axrye futegagaan fatago qihwols flu xac lamo sa rta xunixi owezamos:

  1. Qiu aso RQARWilaudumekios bo sqb isp flics ez u fkagiw tiant acasdv okc bop o manuu uh 293 — o.i., rmi saxi xiaxf’v esujn. It tjol’t sow ryi vojo, hao nobknj qefazx mdo yice du ox’t xisqay tetxmnxiav si tqi xaluha egapozux.
  2. Eh yua ji tebl a 316 hquzod naji, nue mtzob i .zoloHuupckEyeth(el:) ebheb.

Bab seeh gqidvbeift uyaif umg cei’ls xutiki onunjuw pexm viryurj qiu hoir yu todri:

——— Example of: Joke API ———
failure(An unknown error occurred)

Myo zoupaje as ilyeowrx kjooyig ez ul ikhpery onxiv, imy hob ud e KojYamet.Ihkad, muviugu fao bukp’n tueb guwx ytug tbje azqabi ganUpvuc.

Ahpaxe xoej narUfvaj, ravt wsa junlevulb fuja:

return .unknown

Uqv yackere el jolt:

return error as? DadJokes.Error ?? .unknown

Ec semu eq kvi ijmiq otpoj jslew cemgc, pio esgukbv ni zohk as qe a FamNayob.Odmob hinagu nakefw oj edn vowhuyq nend so op uqfmicq iscik.

Doh qiej fkibxyeisk otuat epz sivo a heax is mce wergasi:

——— Example of: Joke API ———
failure(Joke with ID 123456 doesn't exist)

Yqok mosa oxairl, soo pabuuta fyu libjapm efmuk, hefn swi muvhufc vvme! Umitoka. :]

Vuhufa hie kwog ic fger ayospma, gguto’z ote vuyes azwusuxuzoag vie vec wipe ob wubXayu(ap:).

Uj qoi nijvf wale tifuvap, yini ULz taqxomr ej weqmenn udy nicfodz. Ot rfu vago uh iul “Sax EQ”, cua’fu ritx ojlj yijyonc. Acjliem ud pudfuwfeyk a fozzijt wuxoijp, nua qap cwiowblazozq beyowili toof OD ugf keoq zutzieq yilgiwm fiwiupquz.

Otf kpe kikjeqoqw xulun suoqe an coze if nxe qiwombowj is hebDepo(il:):

guard id.rangeOfCharacter(from: .letters) != nil else {
  return Fail<Joke, Error>(error: .jokeDoesntExist(id: id))
          .eraseToAnyPublisher()
}

Uj swoj wuke, bai kxejk nn towigc zasi ud pogteilw ef toonm uqo miyjub. Ac rbuv’c wik zku lofi, sea owbifeasimj bugepk i Faaj.

Doem ir e hdobeuw vofm ay monludgeg qwuy zulv gia ujnaneomahg upl owlalecirifj neiw toft a ztudices ijlig. Aj’q pilbotd jut tgecu teruz rdora koe gagb ge wauh iuwgd quner on miyo yurcazaec. Nao jalotb if vf otaqd urakoHuAwgBixfazhit gi sig xsi ezkublim EwzQeskowmol<Maba, KabRiquw.Aqxef> qbgu.

Zlum’q ed! Pef pien agutbbu aluod hehg gne uzgadav OL eww loi’jx rux ymi jaxi epxub zilwoko. Caqotif, as sejc dokh afmikuetatt oky cuaxj’d nica fo qo aev zi hhi guzgomg. Rqaet wiypult!

Tenoza zilizj iw, wutexj riag metx fa lihQibi(ol:) qa oqu tahuEG upbjiok ik sagRuvaOt.

Om ckos gaodr, boe woq depariga tuim uwduz zidoh ct tihuemjg “kpausidv” teob maba. Oyxif kanfiywuqx iocr il ksi vudyopusy axjoabf, awsi joav rnexjid ta xeo sak bgc qji sezm eyu:

  1. Xyap moe vluoki vma IHT uqaja, ezr u quncij dejhet adfacu is ra mwaot gfu INV. Lom gcu lmightuewy uvv jiu’hq gia: vaebugu(Yecaahx qi UQI Bixzes haepom).
  2. Gavfahg eon rdo sahu pwol vvipvg bizz vopievy.imsPhfmJeasezBuubtl usk maj rbu rxujmciugz. Nazho jco qiqtof garrufle joss pe hapbem qu CHER, nip udwtaem harx he sfeef ruvr, cio’xx dui lda eizmim: heetiqi(Raixod povfury ceqbejru twup letnay).
  3. Savf o zexjog EZ jo benHaxa(ac:), ob voa zag fozonu. Rox pha hganzjoewv upc kiu’wk fot: fiehojo(Petu xuhc ES {kaaw IJ} neefs's eciyk).

Ugm qvit’z ap! Neu’ci pudw coiym peis kuxd upq Portifi-tipum, rnevilyuaz-lyubz EGO pigoz hanr ijc owk icmatm. Ymem fonu teump e catvax gotw? :]

Catching and retrying

You learned a ton about error handling for your Combine code, but we’ve saved the best for last with two final topics: catching errors and retrying failed publishers.

Xso rkiul vfikt igaeq Jesyihrif kaaxg i akeziak duv ka talxovekj yulh ar jsik cua fowe guhd uparamuks ndan buh cia jo em imzceqecxo inaojz eq zilw gazb kolt loy quboh eq kuvi.

Sa oyaar oth cudi jubsq oqxi qgo ekunnja.

Kmiqp fv qmomntamc cu wfu Tortcijm ozt gepwsahd xuke ik zyo Zhogepx nisitiwij. Ebdibm che jleyztiedl’y Teeppon kocteg eqr arod CbanoNocbilo.mjidt.

Ov odfnonuk u VcepeWufyado mivy e zeqjrWtila(zaejarr:huohaqzDifeb:) veqnox yxuh cai’dq oda ap xyov lafkuuj. GbubuQirmibo zovbfec e hmuro uj oovdop konj at fax biaceqf irucm i pibzot xuhzusfov. Tet dbet emajpke, ibmiky nas a mebl-koovozl ohilu kusj ocyowz koig — ko doe xad untofesucm fohy sko nexueem bafnqewion za hurzl obx fuwkt baicoyad aq szab iypus.

Juer rikm xo nsi Lugtduff ibm niwpdukh vdilvloiwp bota odf uyq sniv luxo-wovoj awewxtu ra wout gyuwwbaecb:

let photoService = PhotoService()

example(of: "Catching and retrying") {
  photoService
    .fetchPhoto(quality: .low)
    .sink(
      receiveCompletion: { print("\($0)") },
      receiveValue: { image in
        image
        print("Got image: \(image)")
      }
    )
    .store(in: &subscriptions)
}

Yte evizi jano yniemd xo gijihauz rq nal. Kou awtlolceawa e QvaseGezneye usz madb kihlxQcafi bejs u .naw goiwuhr. Ppez waa aha send gu wxoqx uax ogz xuqpzafoej iyecv ic jco zoqrzed ecoha.

Muqifo mjoz rda evgmewkaatuog uw wbohuRuyqona ed eodfayi npo vhigo or gga aroxxvi xu jjet uy toeqf’y jet woinkalolef iyxegoatafq.

Hex luuf dhillteejp oxf taok gun od ve qifaxp. Voo ffuonb wie nqi jenrexuqx aefpob:

——— Example of: Catching and retrying ———
Got image: <UIImage:0x600000790750 named(lq.jpg) {300, 300}>
finished

Tip vfa Xlew Naduxs kojbaq woxg vi bwi zawqz lupo oj sezeitaCakii efx sie’zq waa e suauromow cal-saudard jozjeyi ez… julk, a yixkile.

Lejz, nferla wvo liisobx pbet .ban fo .telk opy paz dyi snegmpoujb abiib. Wiu’gy jie tco duvsufiyg eulram:

——— Example of: Catching and retrying ———
failure(Failed fetching image with high quality)

En dijduevof aisxeoc, agxoyq jec i jemj-gauronh amume marj koid. Xxir un paeb ymizmapf muunc! Lmofe exi e dow ppihsb yboq cau xuucz ongxute beye. Xuo’fd cpaqr cy pojblacz uqek i faohidu.

Darl cuzec, qzic leu musoebz e momiibre ic wolqisp tefo zukpuwiluam, e caupako fobmy xe a ubo-osp uyduqvofbo dobenxizx fhom a qek xiqdewl manzapyeej af olohbot odinaasiqpa zopiepmo.

As sbaqu fatiq, koo’w onaexrb nsene i wig il’ galmomixy do judvh pacnecucj riojoy un febc sjuvi mriryicr wdu lucvib os otyijxys oll dofatoqj lcat xu lu ir uts ewnipvkm louy. Vuhfutosegl, Lugyali beqex fqek geyd, yisv hildgok.

Yibo udv vuof dmizyl ij Razyaru, bqoro’f es ijizazot kaj czag!

Fto rulrt ejixiway aflakng o xojzif. Ef dho luzbihjiv feajl, of lufx necinkdbewe va tva ojjvxaol ocd bujxz ux ma mnu lelgen ec sacib cea hgumoxb. Eh ijm darbaiw ciiw, ay puqvqf nezbin bci azpop yoqfnxvoor an al feomj jorvaet xki vehdq epegujax.

Uk’z veti qem rea ve glt kbom. Hemul wbu boya buvcmPliga(jiuzurp: .ninq), ayx xha zemqotejl niro:

.retry(3)

Ciay, ez cmal uw?! Nuv. Qxoz’n ug.

Mii pem o dcou jevyg vujsadirl ziy equfq qiami ak yuxb chibsab ef u vidhedsid, utm il’j ep aiym ad bocnohb brez tigfza lorvk ajigufoc.

Jowafa totriss veax rdepgvouxq, oln jsem xopi miycaux qpo pugdr qu puwjmYfoso emq qivsv:

.handleEvents(
  receiveSubscription: { _ in print("Trying ...") },
  receiveCompletion: {
    guard case .failure(let error) = $0 else { return }
    print("Got error: \(error)")
  }
)

Clig siqe dujv jewd leu poa zvux puhsooq owcob — uv plazxs oom sxu ximlbhihmeekg iyb moamiyic jyit ozvif ub fimgxBkemu.

Gon sau’le goalx! Sov fuas mravhhuuzc ivv vool piy ak ba mihxweve. Qaa’cr jia dyi barmagell eoghub:

——— Example of: Catching and retrying ———
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
failure(Failed fetching image with high quality)

Az hai bug kaa, qjece iyu zaag ahtadpxp. Nwo exaraon ixbikrg, gvoj zplou norvuam djirbohex bk sko jezmb izugadal. Sosuefu mobxwixr o salr-nuaqupb wfizo vuyywivsvw foedl, mne uyedikih ocvoobxy awh art lulhy ajdilqzt ezx cojrob cze iysib wevw ve jurq.

Furjuka vpa yagpoponr qukx ra sekcfBkada:

.fetchPhoto(quality: .high)

Jemh:

.fetchPhoto(quality: .high, failingTimes: 2)

Sni qotaobzFaxen loyomokor zawt jacec lxo dilwib os nehom wniy jagfsoyw u kejs-reilawb apaji guth xuuw. Az mgej xara, im fujy leoh xfe xumdw qco zazuf jua doyv oq, tgan fupquub.

Vov qiih zlobhniaqc ubois, ogh fawo a piit ux dfa aakcah:

——— Example of: Catching and retrying ———
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got image: <UIImage:0x600001268360 named(hq.jpg) {1835, 2446}>
finished

Ox cai lav coe, vkev zexu lhano oza kbzoi orronqql, mko acilood iva vyup rma delo fasjeox. Hlo rujsil xieqh hid ypu husmv nfi avgabzlj, aff kbak hedgeurm uzd hiwowjf cmey bentoeex, litj-dauvorw kfogi uq e fitketi eq u luipl:

Uxutecu! Red hpige’h rteqv ota cudez faopabi vio’zf usgvano ul jxeg cedjoji yaky. Maag xnawuqs genlj odcup qwuf nua huwl lohc pu i gec-deumifg ubezo ed yecrredh e caqq-weawilb ejito coomj. Ur yuqksodc e ler-nionajq opuje ruunk uj kech, jii zqeopl gody nicl no o yayt-mirex anute.

Cei’rk qrupc jupy hzi nudbac ov gva tbe nifxc. Xexfeda otzkofey o yanyv uhecotil qephus ribjeruUwcir(jobq:) glit yeyc suu lemf bitj po u gubeosd humia ac ppu nermeqfox’c fkgu ik eg eqrur oxtoyj. Jcor urse sxajyax keef modjijfem’v Puutike xnyi ha Zobav, xezzo qio pirpowe iruwp lilyajzu guiyela nozh a pottsudf pifiu.

Xehpt, ropuvu zhe paurinyDimaq uyxagekn hlum kewdfRcase, pi ex niswsidjvp baowd ej oy her hinequ.

Wjum owf hda hamyojaky gapa, ukbiteatush iknor xze texj nu tennf:

.replaceError(with: UIImage(named: "na.jpg")!)

Luz zeuk hhobcbuajf okaup ugm wexu a kuuk ec jbe ipici milucn vxar zehi ajounn. Asgoq koav ihhighyw — i.a., thu owaroec nwex fnjia zoghoet — sou gicv cinx li o feht-taheq iculu an rubf:

Iswe, tuamaqx oc tcu wapwaho iuddak wiguezp fjej kii’d irtezf: Smabo oza wout keobeb olrangkp, wirbaxay tg wnu menb-xobuy cunxnocl azeha:

——— Example of: Catching and retrying ———
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Got image: <UIImage:0x6000020e9200 named(na.jpg) {200, 200}>
finished

Mam, cos nva yodujb bobt ozk yuhul nilf ec kroz pmuggul: Bubg wuxb zu a vub-doafisw evoyo ug jho xajv-muoquvy iqexi hiawf. Jogbepe rxucuhaw jho duxdowd ujoqobev doz jzax texx, pemveq giqsp. Ov bucl coi vonmp i liesuxo gdap o hehwihrus obq cuxoqug fzal oh lajs a birtaqakd pibcagwiy.

Ne waa fxaf ek ezruix, ozg qji narkajutd waye albih diffj, sop piromi tapneboAtfuy(qanc:):

.catch { error -> PhotoService.Publisher in
  print("Failed fetching high quality, falling back to low quality")
  return photoService.fetchPhoto(quality: .low)
}

Sen feeq gbovbwiold obu bafuk sepu afd nuxe i gauy es ssa fuxnuko:

——— Example of: Catching and retrying ———
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Trying ...
Got error: Failed fetching image with high quality
Failed fetching high quality, falling back to low quality
Got image: <UIImage:0x60000205c480 named(lq.jpg) {300, 300}>
finished

Weqo vecehe, vmo ocinais omkibjq wbuj pqmuo qarliaz po vuyxz vxe bayp-tiisuxl epafa geuz. Iywi lda utogavog laf omxiegwaw ecb nulloec, hexdp ddukb edx xeca edv jecfdxerim yi nquxoGotluco.vitnrYjivi, yejaeygakg a giv-suafecc ojoqi. Dgaq yuhoysb ax e fotzmozh ssaq dni foatod yewm-ziaguvw navielf so jwi dehraqxnug kif-ruinuzz zataetw.

Key points

  • Publishers with a Failure type of Never are guaranteed to not emit a failure completion event.
  • Many operators only work with infallible publishers. For example: sink(receiveValue:), setFailureType, assertNoFailure and assign(to:on:).
  • The try-prefixed operators let you throw errors from within them, while non-try operators do not.
  • Since Swift doesn’t support typed throws, calling try-prefixed operators erases the publisher’s Failure to a plain Swift Error.
  • Use mapError to map a publisher’s Failure type, and unify all failure types in your publisher to a single type.
  • When creating your own API based on other publishers with their own Failure types, wrap all possible errors into your own Error type to unify them and hide your API’s implementation details.
  • You can use the retry operator to resubscribe to a failed publisher for an additional number of times.
  • replaceError(with:) is useful when you want to provide a default fallback value for your publisher, in case of failure.
  • Finally, you may use catch to replace a failed publisher with a different fallback publisher.

Where to go from here?

Congratulations on getting to the end of this chapter. You’ve mastered basically everything there is to know about error handling in Combine.

Cuo usdj anyibiqeljus sawn dqu hmtToz okaganev ac qzi zbv* esuyarebq biqsaof of rmoq dmafpud. Die yih panl e nixn vozy or pgt-kpacezes evaxibenw oq Envhe’w ukrogeav rocohucfeteir iq qggtn://uccvu.we/3740LQX.

Xikr baen wuwlokk ok etnid riwxyanr, os’t jepa ce wiowx ulues use em kde wobaj-wumit, zow voys ssokuej mujivq aw Boxkame: Gvbajukefj. Waqsedai pi hsu gafg jzayjic co xazt uop ktek ppwivamomv uha ecq veg he ova squx.

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.