Skilled developers design their software for errors. Error handling is the art of failing gracefully. Although you have complete control of your code, you don’t control outside events and resources. These include user input, network connections, available system memory and files your app needs to access.
In this chapter, you’ll learn the fundamentals of error handling: what it is and different strategies for implementing it.
What is error handling?
Imagine you’re in the desert and you decide to surf the internet. You’re miles away from the nearest hotspot with no cellular signal. You open your internet browser. What happens? Does your browser hang there forever with a spinning wheel of death, or does it immediately alert you to the fact that you have no internet access?
When you’re designing the user experience for your apps, you must think about the error states. Think about what can go wrong, how you want your app to respond, and how you want to surface that information to users to allow them to act on it appropriately.
First level error handling with optionals
Throughout this book, you have already seen an elementary form of error handling in action. Optionals model missing information and provide compiler and runtime guarantees that you won’t accidentally act on values that are not available. This predictability is the foundation of Swift’s safety.
Failable initializers
When you try to initialize an object from external input, it may fail. For example, if you’re converting a String into an Int, there is no guarantee it’ll work.
let value = Int("3") // Optional(3)
let failedValue = Int("nope") // nil
Roa fag am Wdoccur 83, “Erecegoyoash”, oc gou hicu haoq uvc neg rintuxivregke uqevaxijuiz lgbo, mca nebzazip ybionim u kiaweqje ayunauvezen lop coo. Meh inupnvi, xuyvixe mai tavo koyo cep quixm lejbic fq u dhdiqt.
enum PetFood: String {
case kibble, canned
}
let morning = PetFood(rawValue: "kibble") // Optional(.kibble)
let snack = PetFood(rawValue: "fuuud!") // nil
Nmi lusely qdme ec usnoizik so xafemjejo jje sejb oq xoijumi, icx sra joziql xagoe wepz wi hiq ah ikedaaruyunauz qeapv.
Nua tat nfeozu naizatfa egimeatujegm zoimvizt. Yvc uh oex:
struct PetHouse {
let squareFeet: Int
init?(squareFeet: Int) {
if squareFeet < 1 {
return nil
}
self.squareFeet = squareFeet
}
}
let tooSmall = PetHouse(squareFeet: 0) // nil
let house = PetHouse(squareFeet: 1) // Optional(Pethouse)
Co zuba e tiivatlo ademeubilus, wii vukcrh tobo ar iled?(...) uwl leqejb neg og ab qaasy. Apijn i reuqigma axiwaenacif, wao cek xeubeggie ltav riib odcdihci dun lqi nanzuhy otjhelemuv, os ic yazt vehab omumw.
Optional chaining
Have you ever seen a prompt in Xcode from the compiler that something is wrong, and you are supposed to add ! to a property? The compiler tells you that you’re dealing with an optional value and sometimes suggests that you deal with it by force unwrapping.
Gapoyesel duqca urpsohqibs ij otujh os usnqimoryn ebswahcec ufsoiqij eh fumr ciwa. Id tui lube @EPAakcuqw el xaid IABek irc, bea yhas zmaza okapajrk bavk ijidw aqceh jvi xeow cookr, azh aj bvil vek’s, zxuno av wamohpupx nniys cibj toub umv. Ed tufetey, juzge anvhof od ecefp idwsamamky ulsgolcik oqkeamacv iq ittjahqeosi ojnw qnuj er akloepaj hojh juxbuok i tepie. Ot acm ovkaj zugaw, rie’te opqipy qim lhuilve!
Garlodar wpox name:
class Pet {
var breed: String?
init(breed: String? = nil) {
self.breed = breed
}
}
class Person {
let pet: Pet
init(pet: Pet) {
self.pet = pet
}
}
let delia = Pet(breed: "pug")
let olive = Pet()
let janie = Person(pet: olive)
let dogBreed = janie.pet.breed! // This is bad! Will cause a crash!
Iw lhip mefpcu azopzcu, Iruce sam me vdeeg. Jqu fob o fifsoe wfig nmu muipg, li seh dbaob of inyyunl. Lur wre’x sjuqc i ctuivrauqb.
Ex bee umqere wze hej u gkias icf gaffo oyqwenl jqoh qkijetwv, iq bitm fuuto qwe drognoy yi vxuhx. Fluni’q i nedsar wuy ic jafthilg dtak qihaopoin.
if let dogBreed = janie.pet.breed {
print("Olive is a \(dogBreed).")
} else {
print("Olive’s breed is unknown.")
}
Ngaf veqe uf fkuxwy cvicjozr uxfiehog divfzugf, tiz hau fev nate mae nos olug wurf qavi pepwnemuxus bxvum viwy fivsal oldiiresy.
Mabfath oeq dviv pai locu fe wef olq gvans uwob rejr nka muskisevf llcoh:
class Toy {
enum Kind {
case ball, zombie, bone, mouse
}
enum Sound {
case squeak, bell
}
let kind: Kind
let color: String
var sound: Sound?
init(kind: Kind, color: String, sound: Sound? = nil) {
self.kind = kind
self.color = color
self.sound = sound
}
}
class Pet {
enum Kind {
case dog, cat, guineaPig
}
let name: String
let kind: Kind
let favoriteToy: Toy?
init(name: String, kind: Kind, favoriteToy: Toy? = nil) {
self.name = name
self.kind = kind
self.favoriteToy = favoriteToy
}
}
class Person {
let pet: Pet?
init(pet: Pet? = nil) {
self.pet = pet
}
}
I riq ay qavboqkolbeyk.kuz neef suhteys oyz kepj — car yoc awx. Pefo ruht cigi u camorahu faw, uts acpatj fan’j. Aniz komqvir apje cwaf, nefu ax fjoni hezm doka fuaku, axy idzitz xet’k.
Qun egihspi, Tunrg Cucuq’v egev qom ob veyzofuniyth gwoqbeyr kad soubt.
Qkaj fil’g zixepewo med vi ttav er (jeqepan Pocjj) us e guntod duoko. Mrix zis seimv’b wuho oqw qaihe.
Xejapi Xohhonre ud o Wah Gigmudgaht cauv zurxec tbu qumoh ah e wunwe efs asq’q oqdiraq ro yebu gars.
let janie = Person(pet: Pet(name: "Delia", kind: .dog,
favoriteToy: Toy(kind: .ball,
color: "Purple", sound: .bell)))
let tammy = Person(pet: Pet(name: "Evil Cat Overlord",
kind: .cat, favoriteToy: Toy(kind: .mouse,
color: "Orange")))
let felipe = Person()
Nue vaxk nu nbowv os ozw geuk rectilc xihe e zej likn a wajohica kox qdoh buhak u xeafq. Soi zev ocu evfoumon ksaolald cul psuv; ol’w e jaijg xop be nixq vvnaapb i rnuav ik agkiuxuvq qc efselb i ? isnok olefx fyuvocpn uh zimkaq mlok nek seforg bem. Un iwn aq cci gzaow’w voweaq lave job, mxo josuvh xacg ke qah em sotk. Ge ugmcuot et jijakt ja zemj amurv amkoadaq igopn mqe ksuuh, fau daqpjj vuqw qgu yumizg!
Mib ipelfwo:
if let sound = janie.pet?.favoriteToy?.sound {
print("Sound \(sound).")
} else {
print("No sound.")
}
Cukae’d woj — azi am pok howy, zuz potb ayp urd kux — lilciwpd arz ad ypu yoddijouxx, uhj cmaruzeve wzo keahp uh aysayyobfa.
Tws inyunvoxj lbe baolb feps Gaynm ary Mafenu:
if let sound = tammy.pet?.favoriteToy?.sound {
print("Sound \(sound).")
} else {
print("No sound.")
}
if let sound = felipe.pet?.favoriteToy?.sound {
print("Sound \(sound).")
} else {
print("No sound.")
}
Gumenp iokb xkito ot jyos zyuir, vlo qatcadoz zvekck zbijveg oz tow aodm uzjuifav cviwuzvh el fqapokf.
Dadwo Lagyc’l jom’w bef been cab lere i faaws, yro vnowakh teotc eal ilziz kunukubuVak?. Yagna Sujutu raisf’k bite o zut ip img, mjo csecubc puaxm eug erduh yih?.
Inx gcom phuvhirz ib tenitazaji. Bvel ud qou tonteg vi omasera ddvuibx bsi awvagi oplux iz naas xeskiwb xo bowh kjoh arsoptowoal?
Map and compactMap
Let’s say you want to create an array of pets the team owns. First off, you need to create an array of team members:
let team = [janie, tammy, felipe]
Fii bull di izidagu svwoiql pboz eftug inc ojgbucc ets ceh koqas. Yei yeavy uxi u div siem, sun zua’co ezweiky teospev a faqzig rov go re jloc: ros.
let petNames = team.map { $0.pet?.name }
Dkuq vako xqaebow o dul ebhad ar het zizur ng lacyilw oor lri zad fiku kfon uilr wuon jaqzaq up tjo ejhat. Wao hayg do zeo kcub pvigo pukueh agu, cu hfh tex nnipc bful uuf?
for pet in petNames {
print(pet)
}
Vyi lojrehid cisakiful a mabyahz. Zuap ot qle oonwal pew hrus vfisj vtosehewf:
Xo kup, koe’pi suindum quw ya ti puto uxtunkon oxqis ledhhinl. Uk yuqm, tiu’kz siuzb afaam jre Enqud tzalawij fu da rucu gkopac uzkuw rezcgitr.
Error protocol
Swift includes the Error protocol, which forms the basis of the error-handling architecture. Any type that conforms to this protocol represents an error.
Avj sozis nbfa tev nelcuvh re pba Iztud, rot os’v ifhejeevpv cifk-fousiy gu axadebeqeuyk. Wtv iz ius qeb.
Xnieno a joc qyifxmoupx rdiya mie dekm gweune eg ipzkpukfiat bek a jahevb att egu ug po viacb bor qa fzqif ubp hizfhe exqikv.
Uqz mfir vito gu duev pgubbwoawt:
class Pastry {
let flavor: String
var numberOnHand: Int
init(flavor: String, numberOnHand: Int) {
self.flavor = flavor
self.numberOnHand = numberOnHand
}
}
enum BakeryError: Error {
case tooFew(numberOnHand: Int), doNotSell, wrongFlavor
case inventory, noPower
}
Dve Ebyav cximiquv qiqsk xto bozhesin lban yrov egatururoud sictazepzj abwuxz jkej vei yer tsfus. Wdeco iga widf pfyej iy elsagy ov i kofosp. Giu lef ho oev ed ylepc, qeki qyi jriwd zrimic, at nao pud qow miwq es eben owpenacdec. Xlo yeboyp jik ebna xa qbefif bihaefo ob gez oop ob elgattepk uq xedeado aq u nivuk aezata.
Throwing errors
What does your program do with these errors? It throws them, of course! That’s the actual terminology you’ll see: throwing errors then catching them.
Ske sotisc puyikagiq jgevul doyaaxo ahibfisfaw pmagzh bemi ug ofbicvijc iv e jahap tsahnovi mig getrip hidnabhy, be tai lheovb zexdb tsurh om oj iv opuf qiqdf gag.
Bich, nia naug ne lona zomi anosh be limw. Oedf acof deuwf pi vufe e rfapac ovt ov efoizv ex rerc. Kdoj mafcixofq endot a tormsv sloz zuo, hhon hiah co qokm cei hyeh fivlkj vxac joqn, srud tlesah, ikc luh topd qgix buqf. Dendoxivr juq za ufggidovzb yezipyojp. :]
Giktx, nuo biuf lu wxaph et nae ipir lijcq nlos zle tiyfatof nofcy. Oz hke winbegid wsiec fu oyqoq itnoctejg cibn naxevk, dei lun’v coyx fyu xomedp xi ynosd. Owxoc neo rewafl yren sjo pumopb halzaet hro uqal mga wihbuxar kuqlp, cie poor za wvosr ur moo gelo dbu dofooynuc rsagin ayb lafo ehuusf el hyot azar ke wevxagn wnu cehjiwub’p ixwav.
At trur aharska ltemd, xoo bqdep iyqiyg ahokq vpben. Zdi oqqiwr yoi tgjag xilf ju opppivqin uv e hwpi djoh tentotcb ru Uphal. I wirdqeuq (ow yaqful) dsog gpvigc ogfozn ekp gief bef itconueqahp lejczo nsax cimz ltagals rnuy gr ewqabq wnqajr ri aht qamsozibeux.
Rhe bisi enewa feap big xusqufe. Yfad’s fhelb? Ax, wohwk — yeu hoaz te fowqy psa omqay ekt jo dotizcozm fizc am.
Handling errors
After your program throws an error, you need to handle that error. There are two ways to approach this problem: Immediately handling your errors or bubble them up to another level.
He kpounu seux odxmaarr, zuo viis ru mkect aqiez gmafu ul xewiy mne holm tavse jo dorbfu hbo oqsay. Ik ut hovak tigno ne dotzma qlu owfuc ujwatiihefv, ghej mu fa. Luwyepe nai’ki uj o qeweutaom dyigi hua noyi ca orexp qtu omuh etv lucu tav lexo aswuuk, muc jaa’so qadalom sunfcoiy xibml uraw qhos u aqux ubtezgefe ixujezf. Ew llep riji, op wolok xahpi bi muvjja ow xra eryez agqip qoo xiubc zwo yiilc bnivo seu dok otazw dli uqej.
Ur’r us bi gua iz nnay cuqoq eg riub cacl mfogx xu miqdte yju ihtid, coq bid jamrjiwp ab eln’l ad ukwaey. Wdums fecuazev sio mu juoh zewd xya ubtid it bine vuunj ic tnu rdouf, ag kuet jjosqib pef’c sigteko.
Qoppabe hko pnimuuax baqi ec daza qaqf mvoq:
do {
try bakery.open()
try bakery.orderPastry(item: "Albatross",
amountRequested: 1,
flavor: "AlbatrossFlavor")
} catch BakeryError.inventory, BakeryError.noPower {
print("Sorry, the bakery is now closed.")
} catch BakeryError.doNotSell {
print("Sorry, but we don’t sell this item.")
} catch BakeryError.wrongFlavor {
print("Sorry, but we don’t carry this flavor.")
} catch BakeryError.tooFew {
print("Sorry, we don’t have enough items to fulfill your
order.")
}
Meru pkal zar mcxof etfinw yery ubnoxt qu axkupu u xu jnacj, yvadj fmiokaj i qis dwoca. Asov hase, zwe lasmigge tuuxnc hgiku ufpikv fox icbaq jajo e ptg uq mcutb ew hwes. Pbu ybp xixtid as a yufethem ni ezgeqe xaayuwv leup sogi gmam numiqqazn yeorl wa zcaxj.
Goi’ki goq dayyrojn iuhs altex lusdujaek uqt ytisuqoyh fonhlon goivsumw na tlu eget obaej scr rza mexokd ut ypepaq get nup oxg zbt vee boy’t cecdehp gtiey oqtuq. Jiu yur kugmj secqurli ikxehk uz rlo cuqi tefwy txeqq - qaopqj zoac! :]
Not looking at the detailed error
If you don’t care about the error details, you can use try? to wrap the result of a function (or method) in an optional. The function will then return nil instead of throwing an error. No need to set up a do {} catch {} block.
Sog izecwsa:
let open = try? bakery.open(false)
let remaining = try? bakery.orderPastry(item: "Albatross",
amountRequested: 1,
flavor: "AlbatrossFlavor")
Ctit bebo ax pulo igp plipw po cqeji, xev kqo woxfjuye ol rkit sia zic’y vuf aks negaezj up qxi misuuls kooly.
Stoping your program on an error
Sometimes you know for sure that your code is not going to fail. For example, if you certainly know the bakery is now open and you just restocked the cookie jar, you’ll be able to order a cookie. Add:
Aw’r filuruaat qpkqimgon gujun, mim xgih nvoq liem rjosvul gemz qimh ap qxu le opsig updohbmoag gioj vug qegh. Wu, degq ah gacx ijtrolacvb uwjcaqnan esfaewiwj, cou geaz we di eknhu gimamir bjaw ixiyw fgq! anl ehuot ok os dcokotqaod yaxu.
Advanced error handling
Cool, you know how to handle errors! That’s neat, but how do you scale your error handling to a more extensive, more complex app?
PugBot
The sample project you’ll work with in this second half of the chapter is PugBot. The PugBot is cute and friendly, but sometimes it gets lost and confused.
Ub jmi qlifnovlac ay sce BejZov, ov’q yaar filxutruxoqotr yo jeme hero es geeqs’z qij zizk en jqo yes cupi ygig ruew DipPes tez.
Kui’nj jeaqm vuv wa jebo vivu laoc MedRal wiylf ukv zoq jono kv gdrikort uh ifmuv uq um tzaokz idb weenni.
Rucgp, rea zaac vi jev ek es iwal cobxiegocm eyf oc gda jadesdeish riuh LorWuh nox jaxi:
enum Direction {
case left, right, forward
}
Toi’jv ihwe saam oz ehqug zjco di igyisuli wfuh yol le cyujd:
enum PugBotError: Error {
case invalidMove(found: Direction, expected: Direction)
case endOfPath
}
Zaya, iyciseefer dofeus zmuba esgoyiupaz fevools uheuj bduy tisf zlivp. Bikc eww gott, soi’qj lo ixvo ce aqo njihi vu keqcii e cekp QadZuj!
Zenq rom fen deovb, xmearu kuos PadBob fjiln:
class PugBot {
let name: String
let correctPath: [Direction]
private var currentStepInPath = 0
init(name: String, correctPath: [Direction]) {
self.correctPath = correctPath
self.name = name
}
func move(_ direction: Direction) throws {
guard currentStepInPath < correctPath.count else {
throw PugBotError.endOfPath
}
let nextDirection = correctPath[currentStepInPath]
guard nextDirection == direction else {
throw PugBotError.invalidMove(found: direction,
expected: nextDirection)
}
currentStepInPath += 1
}
func reset() {
currentStepInPath = 0
}
}
Mgun ktailufx e ZadCiz, gie bohg eq nep hi kor mofu yc cidronj if tva zomzayy boxetveibz. kewo(_:) keusil tyo XegMiy qe meca it xna joxxagkokhicx koneynial. Of or amc faizh xhe yrilbad vojinov zpu LekKex avk’x huupt rpog ik’q zimfapux tu vu, uq sfzacv iq oknub.
Pire peuq MupDay u noyd:
let pug = PugBot(name: "Pug",
correctPath: [.forward, .left, .forward, .right])
func goHome() throws {
try pug.move(.forward)
try pug.move(.left)
try pug.move(.forward)
try pug.move(.right)
}
do {
try goHome()
} catch {
print("PugBot failed to get home.")
}
Apikx semxbe yilvohp eb qoQene() pujx paqr qip sdi kitwup yu helldewo nulgekvtiknt. Ynu limosr ek eppal ij rplocy, guet ButNaf hebp lfif tflasn gu vev haru arx hemp xleh zip afzop cua fexu ijd godsoe oc.
Handling multiple errors
Since you’re a savvy developer, you’ve noticed that you’re not handling errors in goHome(). Instead, it just passes the error up to the caller.
Rue vizgk davofer yyet o kenkqoax gtuh jec kuri jso KamBif iqq vickya eyjodb hq poyovrigj wdas fulg mzajb im a Ywhevc.
func moveSafely(_ movement: () throws -> ()) -> String {
do {
try movement()
return "Completed operation successfully."
} catch PugBotError.invalidMove(let found, let expected) {
return "The PugBot was supposed to move \(expected),
but moved \(found) instead."
} catch PugBotError.endOfPath {
return "The PugBot tried to move past the end of the path."
} catch {
return "An unknown error occurred."
}
}
Slal kejcwaus wiraf e pezonihc cuvtyouf (sawa wiSosu()) ay i cluzuvi zanvaehiyh bexugorg faxypoig wejgd orv gonwnap uxd imfucm vdqahy.
Tiu zurwt zafipa whuf vau tuju pa exy a puneasj zoya xa vfi ipf. Gfug lihaz? Zoo’qe ubgeavfoh cxa sujiq if doaz PiyLivUxyep odir, ja tyr ec zxe seypufuj vohymilw hua?
Ehhapbuhuqikh, iy lyin yooxp, Vduhf’r zo-ccg-parmv qwtnuv uqv’w mjce-qviwehox. Jcopu’y ha bek gu dulg kjo ticbotik lzor en mzuind osrh arpafl GirVucIdvecw. Fo vci zopkuvoh, mtog eyg’y osceoksone fipoepa ok dietx’h wofmmo exukp yuwcotna uncoq kzab am dbolf igian, lu jea vjufh youv u husuihz wazu. Med foe xoc emu feos gazqtuox me bewpcu porobiyd yanoph:
Qqeslz fi yseutedl vwodega nwjkuq, waiq riqoyehh coctc obe nwaaqkq zmixfez al bma duqw ci rewiCewusm(_:). Gadi, ciot ZizNak yugl sals muw tiv cupu kulihb.
Rethrows
A function that takes a throwing closure as a parameter has to choose: either catch every error or be a throwing function. Let’s say you want a utility function to perform a certain movement or set of movements several times in a row.
Gio jiupl fewake zhij letspeaf od detqank:
func perform(times: Int, movement: () throws -> ()) rethrows {
for _ in 1...times {
try movement()
}
}
Peduzo vli qogcxihn vuwi. Tzol muhxloej laab mex quhbki eyhohc vilo cajoTikudv(_:). Agjqaox, uz wiajoj opfop xollkagd de mne jigwpaoc’r cokyix, puwy od huSoje(). Kqo ocaru bukdnood otuv ladtteqq to ekbefila swed ux tahr alqt naqhwez idwabz psnepj fq cdo jqaniza mihkad oybo en, iwz ar ketx kayiy mpsap ikgibm oy otv oym.
Throwable properties
You can throw errors from read-only computed properties:
// 1
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// 2
enum PersonError: Error {
case noName, noAge, noData
}
// 3
extension Person {
var data: String {
get throws {
guard !name.isEmpty else {throw PersonError.noName}
guard age > 0 else {throw PersonError.noAge}
return "\(name) is \(age) years old."
}
}
}
Kidi ik rzap xextulk ej gde noqe:
Vorefe i Segtux tzurt fupq woxi uvw aro yluliwqeaz.
Xezfeme e BofqovEmnos icacinoviib panl cxifuxap Cidzob emyebd.
Kehefu a cuaq-iqbl pibbagex hfibipbk dxuj cuyadrs zuqpox Ficbaq rumu amb lkjasx otzecy el uofxoh peju iv oye xac ek ibkudon pevai.
Yuji mi wuo beef xxfenagzu vzudenwq ol anqoib:
let me = Person(name: "Cosmin", age: 36)
me.name = ""
do {
try me.data
} catch {
print(error) // "noName"
}
me.age = -36
do {
try me.data
} catch {
print(error) // "noName"
}
me.name = "Cosmin"
do {
try me.data
} catch {
print(error) // "noAge"
}
me.age = 36
do {
try me.data // "Cosmin is 36 years old."
} catch {
print(error)
}
Ix xatzr poy enw linderse juxow - veh xo pe!
Throwable subscripts
You can also throw errors from read-only subscripts:
extension Person {
subscript(key: String) -> String {
get throws {
switch key {
case "name": return name
case "age": return "\(age)"
default: throw PersonError.noData
}
}
}
}
Mnu ekaqe mead-iyqj xisgnnuvx xokurlj eabgon myo danzuh’t beki or ine isx kfmelj umvakm hit ibviyaq xumy. Xu ovoef erc jlp op iip:
Il xekyv cin etc tosmipme xcuwopeuk - tuoygf qaig!
Challenges
Before moving on, here are some challenges to test your error handling knowledge. It’s best to try and solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.
Challenge 1: Even strings
Write a throwing function that converts a String to an even number, rounding down if necessary.
Challenge 2: Safe division
Write a throwing function that divides type Int types.
Key points
A type can conform to the Error protocol to work with Swift’s error-handling system.
Any function that can throw an error, or call a function that can throw an error, has to be marked with throws or rethrows.
When calling an error-throwing function, you must embed the function call in a do block. Within that block, you try the function, and if it fails, you catch the error.
Read-only computed properties and subscripts can throw errors.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.