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?
Update 10/02/16: This tutorial has been updated for Xcode 8 and Swift 3.
Update 10/02/16: This tutorial has been updated for Xcode 8 and Swift 3.
Error handling in Swift has come a long way since the patterns in Swift 1 that were inspired by Objective-C. Major improvements in Swift 2 made the experience of handling unexpected states and conditions in your application more straightforward. These benefits continue in Swift 3, but there are no significant updates to error handling made in the latest version of the language. (Phew!)
Just like other common programming languages, preferred error handling techniques in Swift can vary, depending upon the type of error encountered, and the overall architecture of your app.
This tutorial will take you through a magical example involving wizards, witches, bats and toads to illustrate how best to deal with common failure scenarios. You’ll also look at how to upgrade error handling from projects written in earlier versions of the language and, finally, gaze into your crystal ball at the possible future of error handling in Swift!
Time to dive straight in (from the the cauldron into the fire!) and discover the various charms of error handling in Swift 3!
Getting Started
There are two starter playgrounds for this tutorial, one for each section. Download Avoiding Errors with nil – Starter.playground and Avoiding Errors with Custom Handling – Starter.playground playgrounds.
Open up the Avoiding Errors with nil starter playground in Xcode.
Read through the code and you’ll see several classes, structs and enums that hold the magic for this tutorial.
Take note the following parts of the code:
protocol Avatar {
var avatar: String { get }
}
This protocol is applied to almost all classes and structs used throughout the tutorial to provide a visual representation of each object that can be printed to the console.
enum MagicWords: String {
case abracadbra = "abracadabra"
case alakazam = "alakazam"
case hocusPocus = "hocus pocus"
case prestoChango = "presto chango"
}
This enumeration denotes magic words that can be used to create a Spell
.
struct Spell {
var magicWords: MagicWords = .abracadabra
}
This is the basic building block for a Spell
. By default, you initialize its magic words to .abracadabra
.
Now that you’re acquainted with the basics of this supernatural world, you’re ready to start casting some spells.
Why Should I Care About Error Handling?
“Error handling is the art of failing gracefully.”
–Swift Apprentice, Chapter 22 (Error Handing)
“Error handling is the art of failing gracefully.”
–Swift Apprentice, Chapter 22 (Error Handing)
Good error handling enhances the experience for end users as well as software maintainers by making it easier to identify issues, their causes and their associated severity. The more specific the error handling is throughout the code, the easier issues are to diagnose. Error handling also lets systems fail in an appropriate way so as not to frustrate or upset users.
But errors don’t always need to be handled. When they don’t, language features let you avoid certain classes of errors altogether. As a general rule, if you can avoid the possibility of an error, take that design path. If you can’t avoid a potential error condition, then explicit handling is your next best option.
Avoiding Swift Errors Using nil
Since Swift has elegant optional-handling capabilities, you can completely avoid the error condition where you expect a value, but no value is provided. As a clever programmer, you can manipulate this feature to intentionally return nil
in an error condition. This approach works best where you should take no action if you reach an error state; i.e. where you choose inaction over emergency action.
Two typical examples of avoiding Swift errors using nil
are failable initializers and guard statements.
Failable Initializers
Failable initializers prevent the creation of an object unless sufficient information has been provided. Prior to the availability of these initializers in Swift (and in other languages!), this functionality was typically achieved via the Factory Method Pattern.
An example of this pattern in Swift can be seen in create:
:
static func create(withMagicWords words: String) -> Spell? {
if let incantation = MagicWords(rawValue: words) {
var spell = Spell()
spell.magicWords = incantation
return spell
}
else {
return nil
}
}
The above initializer tries to create a spell using the magic words provided, but if the words are not magical you return nil
instead.
Inspect the creation of the spells at the very bottom of this tutorial to see this behavior in action:
While first
successfully creates a spell using the magic words "abracadabra"
, "ascendio"
doesn’t have the same effect, and the return value of second
is nil
. (Hey, witches can’t win all the time).
Factory methods are an old-school programming style. There are better ways to achieve the same thing in Swift. You’ll update the Spell
extension to use a failable initializer instead of a factory method.
Delete create(_words:)
and replace it with the following:
init?(words: String) {
if let incantation = MagicWords(rawValue: words) {
self.magicWords = incantation
}
else {
return nil
}
}
Here you’ve simplified the code by not explicitly creating and returning the Spell
object.
The lines that assign first
and second
now throw compiler errors:
let first = Spell.create(withMagicWords: "abracadabra")
let second = Spell.create(withMagicWords: "ascendio")
You’ll need to change these to use the new initializer. Replace the lines above with the following:
let first = Spell(words: "abracadabra")
let second = Spell(words: "ascendio")
After this, all errors should be fixed and the playground should compile without error. With this change your code is definitely tidier – but you can do even better! :]
Guard Statements
guard
is a quick way to assert that something is true: for example, if a value is > 0, or if a conditional can be unwrapped. You can then execute a block of code if the check fails.
guard
was introduced in Swift 2 and is typically used to (bubble, toil and trouble) bubble-up error handling through the call stack, where the error will eventually be handled. Guard statements allow early exit from a function or method; this makes it more clear which conditions need to be present for the rest of the processing logic to run.
To clean up Spell
‘s failable initializer further, edit it as shown below to use guard
:
init?(words: String) {
guard let incantation = MagicWords(rawValue: words) else {
return nil
}
self.magicWords = incantation
}
With this change, there’s no need need for an else
clause on a separate line and and the failure case is more evident as it’s now at the top of the intializer. Also, the “golden path” is the least indented. The “golden path” is the path of execution that happens when everything goes as expected, i.e. no error. Being least indented makes it easy to read.
Note that the values of first
and second
Spell
constants haven’t changed, but the code is more more streamlined.