Magical Error Handling in Swift
In this tutorial you will learn all about error handling in Swift. You’ll learn about all the new features added in Swift 2.0 and discover how to use them. By Gemma Barlow.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Magical Error Handling in Swift
30 mins
- Getting Started
- Why Should I Care About Error Handling?
- Avoiding Swift Errors Using nil
- Failable Initializers
- Guard Statements
- Avoiding Errors with Custom Handling
- Refactoring to Use Swift Errors
- Handling Hat Errors
- Handling Familiar Errors
- Handling Toad Errors
- Handling Spell Errors
- What Else Are Custom Errors Good For?
- Catching Errors
- Propagating Errors
- Manipulating Error Handling Behavior
- More Fun with Errors
- The Future of Error Handling in Swift
- Where To Go From Here?
Avoiding Errors with Custom Handling
Having cleaned up the Spell
initializer and avoided some errors through the clever use of nil
, you’re ready to tackle some more intricate error handling.
For the next section of this tutorial, open up Avoiding Errors with Custom Handling – Starter.playground.
Take note of the following features of the code:
struct Spell {
var magicWords: MagicWords = .abracadbra
init?(words: String) {
guard let incantation = MagicWords(rawValue: words) else {
return nil
}
self.magicWords = incantation
}
init?(magicWords: MagicWords) {
self.magicWords = magicWords
}
}
This is the Spell
initializer, updated to match the work you completed in the first section of this tutorial. Also note the presence of the Avatar
protocol, and a second failable initializer, which has been added for convenience.
protocol Familiar: Avatar {
var noise: String { get }
var name: String? { get set }
init(name: String?)
}
The Familiar
protocol will be applied to various animals (such as bats and toads) further down in the playground.
This clearly isn’t Hedwig, but still cute nonetheless, no?
This clearly isn’t Hedwig, but still cute nonetheless, no?
struct Witch: Magical {
var avatar = "*"
var name: String?
var familiar: Familiar?
var spells: [Spell] = []
var hat: Hat?
init(name: String?, familiar: Familiar?) {
self.name = name
self.familiar = familiar
if let s = Spell(magicWords: .prestoChango) {
self.spells = [s]
}
}
init(name: String?, familiar: Familiar?, hat: Hat?) {
self.init(name: name, familiar: familiar)
self.hat = hat
}
func turnFamiliarIntoToad() -> Toad {
if let hat = hat {
if hat.isMagical { // When have you ever seen a Witch perform a spell without her magical hat on ? :]
if let familiar = familiar { // Check if witch has a familiar
if let toad = familiar as? Toad { // If familiar is already a toad, no magic required
return toad
} else {
if hasSpell(ofType: .prestoChango) {
if let name = familiar.name {
return Toad(name: name)
}
}
}
}
}
}
return Toad(name: "New Toad") // This is an entirely new Toad.
}
func hasSpell(ofType type: MagicWords) -> Bool { // Check if witch currently has an appropriate spell in their spellbook
let change = spells.flatMap { spell in
spell.magicWords == type
}
return change.count > 0
}
}
Finally, the witch. Here you see the following:
- A
Witch
is initialized with a name and a familiar, or with a name, a familiar and a hat. - A
Witch
knows a finite number of spells, stored inspells
, which is an array ofSpell
objects. - A
Witch
seems to have a penchant for turning her familiar into a toad via the use of the.prestoChango
spell, withinturnFamiliarIntoToad()
.
Notice the length and amount of indentation in turnFamiliarIntoToad()
. Also, if anything goes wrong in the method, an entirely new toad will be returned. That seems like a confusing (and incorrect!) outcome for this particular spell. You’ll clean up this code significantly with custom error handling in the next section.
Refactoring to Use Swift Errors
– The Swift Programming Language (Swift 3)
recoverable errors at runtime.”
Not to be confused with the Temple of Doom, the Pyramid of Doom is an anti-pattern found in Swift and other languages that can require many levels of nested statements for control flow. It can be seen in turnFamiliarIntoToad()
above – note the six closing parentheses required to close out all the statements, trailing down on a diagonal. Reading code nested in this way requires excessive cognitive effort.
Guard statements, as you’ve seen earlier, and multiple simultaneous optional bindings can assist with the cleanup of pyramid-like code. The use of a do-catch
mechanism, however, eliminates the problem altogether by decoupling control flow from error state handling.
do-catch
mechanisms are often found near the following, related, keywords:
throws
do
catch
try
defer
Error
To see these mechanisms in action, you are going to throw multiple custom errors. First, you’ll define the states you wish to handle by listing out everything that could possibly go wrong as an enumeration.
Add the following code to your playground above the definition of Witch
:
enum ChangoSpellError: Error {
case hatMissingOrNotMagical
case noFamiliar
case familiarAlreadyAToad
case spellFailed(reason: String)
case spellNotKnownToWitch
}
Note two things about ChangoSpellError
:
- It conforms to the
Error
protocol, a requirement for defining errors in Swift. - In the
spellFailed
case, you can handily specify a custom reason for the spell failure with an associated value.
ChangoSpellError
is named after the magical utterance of “Presto Chango!” – frequently used by a Witch
when attempting to change a familiar into a Toad
).
OK, ready to make some magic, my pretties? Excellent. Add throws
to the method signature, to indicate that errors may occur as a result of calling this method:
func turnFamiliarIntoToad() throws -> Toad {
Update it as well on the Magical
protocol:
protocol Magical: Avatar {
var name: String? { get set }
var spells: [Spell] { get set }
func turnFamiliarIntoToad() throws -> Toad
}
Now that you have the error states listed, you will rework the turnFamiliarIntoToad()
method, one clause at a time.
Handling Hat Errors
First, modify the following statement to ensure the witch is wearing her all-important hat:
if let hat = hat {
…to the following:
guard let hat = hat else {
throw ChangoSpellError.hatMissingOrNotMagical
}
}
at the bottom of the method, or else the playground will compile with errors!
The next line contains a boolean check, also associated with the hat:
if hat.isMagical {
You could choose to add a separate guard
statement to perform this check, but it would be clearer to group the checks together on a single line. As such, change the first guard
statement to match the following:
guard let hat = hat, hat.isMagical else {
throw ChangoSpellError.hatMissingOrNotMagical
}
Now remove the if hat.isMagical {
check altogether.
In the next section, you’ll continue to unravel the conditional pyramid.