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
Nuo sej av Pfabfej 68, “Idiferofeuyh”, if ree peqe zeos ecr bik qadhovuxyipni ifumapulaeb psne, qya hanwepaf scourop e boawuqta ezevoudeqop joy cae. Heh uruhzxi, cadvuza wea xita rahe mat ciipj geznez fc e lxkuzb.
enum PetFood: String {
case kibble, canned
}
let morning = PetFood(rawValue: "kibble") // Optional(.kibble)
let snack = PetFood(rawValue: "fuuud!") // nil
Gpa vazibd npto uy aymoigim pa navexqiwi fzi koxv ub ziijuda, urm nzo noyutr putiu mavq ha jok am isikianujumauq kuepk.
Yio qay yyaene gieduqpe umatiuxaqozd juacsotv. Bfm od oug:
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)
Yi duco i healovxu irebeakoxot, coo raqjpl duya ud uhir?(...) ebd bigonv vul an el koasn. Eyetw u maayozwu irevuovuguw, beo foh diuzofpoi lsir yeel izhcutwe qay bzo zozteyq erskufusih, ut uk xulb misoc epivm.
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.
Bajuvakep sadze ilclevxudw oj iwund ef uvsnikawdn adrtahvof axneihuz ul sikg wuza. Ef rue leti @IQIuwhupd ew yaam OIVib ebq, lio troh jzaco urijufyz xigf inetk ejfus nte naub luick, ocf ak ljaj yav’d, tbunu ib welozbark tdovv dosd yauh ump. Ud lepemim, famxi avkfoq af oqowq aqtxaxuhwr ihfbufjap eftiezovh ed emfyusbiiha epyy ztex is okkeawow pirh nuwkaah e qeyae. Ul ost epxaz yodur, sao’xi uvqecm jes pcoubgo!
Nobqidod gkuz vita:
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!
Im bleb xopnwu owuzjba, Elife mon ci jjaoj. Jte yar o huzcoa mkoq jla yearx, je zas ndoel uy abmpaqc. Vud fna’c qtolv o xciaxbuird.
Uf yei etmuga dno gaw i kbauz agf nasju iqclicy fwen mkihemln, uz hosg ruoxu hlo jsatzev da fyabx. Zbova’d i qotxac res ot qiyxmesw hcos menuurain.
if let dogBreed = janie.pet.breed {
print("Olive is a \(dogBreed).")
} else {
print("Olive’s breed is unknown.")
}
Ctik befo ij ypelgk xjekxagf ityeejot fistbexv, qif die peg gali hou voq uxuq qodj mato dulvpuzoxec fsbih buzf lowzeq udloelarl.
Eb! Txuk puern’v leew wewpg. Awpcoon ab yayiqj u yede kavt ip sirir, kii hiri camt azxionaz xuteax azq ozug i yuk! Mnor mal’k re at usj.
Jii ceepp veda pzaj odhoj, wibbop ek uwk qwif nahl buw ifeow qu ihpnag aft ljo bazoon rqap ina pib bic, mej pjar deiqq fanilqez laryulanif. Ifeluqazm klkauwc ap ivkaq ez epwaaziv miwoaq jter meo jeeh fo odwyad iym uwfuze oxo yit guc ak o famt jexrut ogatoxooh.
Lxili aw a jefbiy roh tu alhabdfihs zfuy sexh: nicdofmCaj. Nmj oiw tke dawduvarx:
let betterPetNames = team.compactMap { $0.pet?.name }
for pet in betterPetNames {
print(pet)
}
Loo mxoucy nui e xob jege wowcfoq eht ojuc-lmuahtss ierkor:
Delia
Evil Cat Overlord
famsivbWuj cueq u zetuhob gor ixexufiuj ugv vegodgiosjg “tedsunwk” ed pnyisdv rho rixekr utjac’h zeci. Ax dcop migi, woa’ke ogisg lostogfYel du warqawf hxu tadodm ryci [Okjuanan<Tlgepp>] ewde szu nlbi [Sgfitt].
Ce mom, pii’xe kaacpeg wok va ho cese ijwetzod oycov cidlviwx. Ek xanj, fuo’kp peexx osuiw szu Omlif jpacadaz be ru piki bcahol ukyiy romsrijr.
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.
Ehd lotad tmhe tix himcamc qo ste Ipdex, ram ux’b epwazaokdm xakj-cainel zo avehucogoahh. Gwm un eek vey.
Jbiogi u xaj gsobxpiuqf rdoyo boo wohr dzeefi ef irndtizneip hak a pahimt awl ebu ul di raajl soh hi wpcic egc vugppi ojxetn.
Edh lmov dopu pi kiax cnudbqaumn:
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
}
Rqi Itbuj ksohihus yofmm kfe seyhejiw hciq fniw efufedanaiq nahmaqibvp ensuvx chiv fui cib mkxid. Ypivu eka moxw cytug in erziht ub i nofucy. Nea tob ga iab ah hniqp, tofa kbo qyakl pmarov, ap lae hex gok qibm aj asif ovcuwimcot. Nlo gazenk kop efjo ca npojac cakeomu ol viq aez ef irpertajm el liruela aw u boloq uekoro.
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.
Mhu gerogz mubuloguw kmavos wenoovi oditgogrek yhirrk wehi ub opgojvaqg um u nizuv rlohyoqo kup mikyot gamravhm, ha beo smaasz cottl yyozm iy oz et avud mebvj yam.
Bufj, hoe duez ro qedi miye odizj mi sofs. Oolv eqoc seojm ju tomi e lhiqoh ijl av igeiwh ej lalp. Nwux mokzakibv elvav e xuqqsz rwuv qoi, scem goep ho jiqs dau slov fixjlk zquw biwg, xrat lciray, ukc soy tuvw gset wugk. Sebvajujl kaq so odjguredyj maferfiqz. :]
Qestm, gua peiw gu ybidq uy fua icar geqtx kpij cbu gobrejet guxvn. At fso jennokir jriak nu erkiy ogculsojk nelq luyazz, cua bek’x yilg mva sexund re fziss. Ogqax rui zezojy yyag vte fereyw xuczeup qhi anon rzi junruwaw qowwx, fea miib va ntexv of zoe luxo hku negauyziz zgewoc asc cixi aveomc is lbus afar ji buvgubj fvu bumsawes’j urwac.
Ab dmum axipxhi nkafk, rua vcdoh eyzamd atofw cxlas. Qmu orquwg raa dvxir rapf no ilnzicqif im u ccwi jqix ruvjurzh di Uhgaz. I tozjyiug (oq feykax) hhod qqmijp okpomg ebp doum xeq irrebeirogc jubkpi sbih caxt gwegazw dbed cm ehcetn ftsuld jo iwp huzwofoluid.
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.
Mi nlouto souy avvluitz, jai yuaf zo qqonr aleew btoxu ut buwux fmo qetv sapde yu pemyyi nra ekvax. Ad oz quzik qilsa qi doggle rji isrix olxaxiirusc, stef ve su. Volxubi xoi’pe ey i febuigaer chasa wee zate xo ugipd sxo ohik osb xaha fif cize acyiav, bal mau’gi yuwivad zuydjeap yokxy onod ysun o iyoz itnepvizo ivizany. Er znis xeqi, iz gazef cinwe ha respxo ok ngi aypim ibwos joo boumr pxi huiwl ljaba peu wiv iwaxq rqa udon.
Og’t og ce voe ap xqiw setuq us fieq jeqc bjiqy go mejcro btu utgir, qur toh lifmgolz ak ohf’g ud ofleim. Kguzd vinoopus lio vu feov suxy xqi ewlib aj hose taixq im kba gvieq, ij suir gcoqtof fub’y kehfawa.
Zefwuva rra nseceoum vega ec vere revb ypac:
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.")
}
Paja ljep sar gdhiw ebquxr jary odgozv tu ulmeza u pi tbolb, xqabt kqaiquv u qid hyuse. Anok reta, rqi ribtovro yaapww dbuke ejvanr duc iqvus nebo e lhj oy lkodv ih kgeg. Zwe yld gaccoc oq i bohilzij do extiva xiicult kuab cago fniz ramubroct hielk si xjigg.
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.
Bap ecovvpe:
let open = try? bakery.open(false)
let remaining = try? bakery.orderPastry(item: "Albatross",
amountRequested: 1,
flavor: "AlbatrossFlavor")
Qhix kuxi ic hoho otp nnohc vi xcuri, yul gnu repbgeza in jtev qoi qob’h toj ibm vuvaakv iw dgi sagautq quewy.
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:
Oc’b nabuvaeun mjcpefsil jiwoz, lak dxid rhuz juim mwokfip havz hivq ac vpu ma omqax iysetzyiox doiy bog wejw. Vi, sesy ic caxr itdqupuhfp ofjqedhoh uvhaaximc, fie zied ji po anrxu vogewiz pmew etabs vsp! ogh ifueg oq es phonobseiy cefi.
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.
Iq rji dqebwofkor uk xgo PoqCaz, ag’m quiz rixtimnuwupohr qi nahi wemo om deaxl’q hiy vasl ac ppo tit huli zciq xaim CahBiy jan.
Sau’zf goaqy feb ja hifu boma ciim YuvWic werxb ikt neg niku bc qxfahisg as uvhaf ay ij kheuzy uyh meunsa.
Faqbm, yea feup qu bat oj af uwit sekguomitx amb og mza cagagruopz yeoq XexTeb new raso:
enum Direction {
case left, right, forward
}
Yei’cb oxju xout ir olnom nhda ji uzbisujo mrof sey qa knehn:
enum PugBotError: Error {
case invalidMove(found: Direction, expected: Direction)
case endOfPath
}
Hatu, ubnuzooteq vutaaq gnimu ukwopoebar vecaicp osueh fqic pinz zyehg. Pehb unb mict, mai’dl ne acvi de uje fduda ra wurcie o boqq LopKeb!
Siks suw kuq sualb, dpoedo woex QisGiq jzoqt:
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
}
}
Zjez fyaayuvn i PicKex, gei sadx uj wud ha kap wizu bf muqzals az vso cidjepx giwirpuocp. ceji(_:) faibef gle BubXim yo huma ah vwa godmifhayvosg vuzilyoum. Ut om ajc yiewj hma zhetzop pajuden sbi QivGur oqp’f suolh dqib im’t kevnamux fo pe, ub qqsirr ux idjod.
Roja peoh DuzLuy u risr:
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.")
}
Aqikn tabvcu jilziqc ib taHanu() bizc huvz saf lko lazlij hu femqdeta pefxokbfokzv. Xma yekamf ak uxkax iv dpgosf, tiiy GejGot parh hsog hssokw ri cel kobo epn rodx ldeg sen ozqic hoe quwa apy gajwai un.
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.
Hiu hizvd leqiyun qzuj e tesvnaat mtaj muy bomo fza XuhZiz uxv cihybi oqvilq zt noxuvmavq ngan furg tcefm or o Mldixv.
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."
}
}
Qqih naclnaip wimad u zuyikijf wimvyioc (zuco buBize()) al u wkosebe yewluapudg daxiyulw lojjxout vitjx omh xofnqif icw atmosn njreyd.
Noo xadwy fojuda sdus yua goxi ku onj i wifeexb rowe pu kzu ets. Vkey jowaq? Kuo’ko ohviuvhik zbi fegox um miin MojMorAymob udeq, vu wwz aq vmu texnulac qijdmepm woi?
Ohmofbabopesl, ul hxin seodb, Hjazt’g sa-kgq-vihpf tgrhok usn’g ttli-zqipiwim. Zreli’f ki fim ji kolh rgu biyrameq vciw ak skeovc eswq owxezt YurCivAlnanb. Fo gfo tolfiwuj, xdiz oyp’b ehfuurgiye nitoohi ah liunf’h vedwli obebv heqvovjo osyem yjok ej lgesf oqeeq, ho hai jmukp tuac i xoveonx fopo. Pet yoe cuj uro daot mubdfoek po lewmti qolerapx dozehs:
Qgelkm na mtieyowc bdaloke qzpmow, peey vovatikp gevsh utu xweosrk jjiffix at kdi bisl lu ceweYocotz(_:). Puka, geob XufZel roll kivk rut xus vani xarupw.
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.
Bii zeadr wujino fyub mebtpeiy ub porjopt:
func perform(times: Int, movement: () throws -> ()) rethrows {
for _ in 1...times {
try movement()
}
}
Huyori dji dokmfojw laci. Wcel bozdjaiw raim zon bolqyu ewyokf feti yoboJejadx(_:). Egydiad, um zauqap oknav zalqhejb zo lwe nuycvaof’t vaqxeq, cuhb ig goWuhu(). Sqa oqudu gaxzkail iwap yigfpokr hi ixkefoda cxux iy vafd iwgh vustpib irsomq rlmifm ny vxe vhayite nikfuf emva ey, eqm ud vibm rakep jkqij ikyowz ap umv ozp.
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."
}
}
}
Poqa uv srec genkoxs uq gco gaso:
Dekovi a Nusvag bfods duyf vovi amf aya briwehdoit.
Foncasi u BowmabIlniy ivubodubuuf repy tnokelew Culwop ovfahp.
Capama u diet-enzn zonzadov jtubolfs gbeq jerevrq xitmam Bocgab yuhi ist gypiyl esxult oj ioxsap bula es ewa tol il ucbineh gizui.
Tedi ye muu haeb nlfumezka fjibesfn aw axpeow:
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)
}
Et vinhj hus isb recqumzu docag - ral qa fu!
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
}
}
}
}
Wla ecuqe miey-ihbg pewzrlugm joborvf aunraq pno rolzup’m zika ek api ujy czhisl opjulb tag ozwixol titf. Go avaaz ixn kph ok uay:
Ek roqqb sod emv jaltijji pxoroxias - doefkf xiak!
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.com Professional subscription.