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?
Propagating Errors
throws
The throws
keyword is required in Swift if a function or method throws an error. Thrown errors are automatically propagated up the call stack, but letting errors bubble too far from their source is considered bad practice. Significant propagation of errors throughout a codebase increases the likelihood errors will escape appropriate handling, so throws
is a mandate to ensure propagation is documented in code – and remains evident to the coder.
rethrows
All examples you’ve seen so far have used throws
, but what about its counterpart rethrows
?
rethrows
tells the compiler that this function will only throw an error when its function parameter throws an error. A quick and magical example can be found below (no need to add this to the playground):
func doSomethingMagical(magicalOperation: () throws -> MagicalResult) rethrows -> MagicalResult {
return try magicalOperation()
}
Here doSomethingMagical(_:)
will only throw an error if the magicalOperation
provided to the function throws one. If it succeeds, it returns a MagicalResult
instead.
Manipulating Error Handling Behavior
defer
Although auto-propagation will serve you well in most cases, there are situations where you might want to manipulate the behavior of your application as an error travels up the call stack.
The defer
statement is a mechanism that permits a ‘cleanup’ action to be performed whenever the current scope is exited, such as when a method or function returns. It’s useful for managing resources that need to be tidied up whether or not the action was successful, and so becomes especially useful in an error handling context.
To see this in action, add the following method to the Witch
structure:
func speak() {
defer {
print("*cackles*")
}
print("Hello my pretties.")
}
Add the following code to the bottom of the playground:
func exampleThree() {
print("") // Add an empty line in the debug area
let witchThree = Witch(name: "Hermione", familiar: nil, hat: nil)
witchThree.speak()
}
exampleThree()
In the debug console, you should see the witch cackle after everything she says.
Interestingly, defer
statements are executed in the opposite order to which they are written.
Add a second defer
statement to speak()
so that a Witch screeches, then cackles after everything she says:
func speak() {
defer {
print("*cackles*")
}
defer {
print("*screeches*")
}
print("Hello my pretties.")
}
Did the statements print in the order you expected? Ah, the magic of defer
!
More Fun with Errors
The inclusion of the above statements in Swift brings the language into line with many other popular languages and separates Swift from the NSError
-based approach found in Objective-C. Objective-C errors are, for the most part, directly translated, and the static analyzer in the compiler is excellent for helping you with which errors you need to catch, and when.
Although the do-catch
and related features have significant overhead in other languages, in Swift they are treated like any other statement. This ensures they remain efficient – and effective.
But just because you can create custom errors and throw them around, doesn’t necessarily mean that you should. You really should develop guidelines regarding when to throw and catch errors for each project you undertake. I’d recommend the following:
- Ensure error types are clearly named across your codebase.
- Use optionals where a single error state exists.
- Use custom errors where more than one error state exists.
- Don’t allow an error to propagate too far from its source.
The Future of Error Handling in Swift
A couple of ideas for advanced error handling are floating around various Swift forums. One of the most-discussed concepts is untyped propagation.
– from Swift 2.x Error Handling
– from Swift 2.x Error Handling
Whether you enjoy the idea of a major error handling change in a future version of Swift, or are happy with where things are today, it’s nice to know that clean error handling is being actively discussed and improved as the language develops.
Where To Go From Here?
You can download the finished set of playgrounds here for this tutorial.
For further reading, I recommend the following articles, some of which have already been referenced throughout this tutorial:
- Swift Apprentice, Chapter 22 – Error Handling
- Failable Initializers
- Factory Method Pattern
- Pyramid of Doom
If you’re keen to see what may lie ahead in Swift, I recommend reading through the currently open proposals; see Swift Language Proposals for further details. If you’re feeling adventurous, why not submit your own? :]
Hopefully by now that you’ve been truly enchanted by error handling in Swift. If you have any questions or comments on this tutorial, please join the forum discussion below!