So far, we have not had to be too concerned about what happens in a function after we throw an error. There are times when we will need to perform a certain action before exiting a function, regardless of if we threw an error or not.
An important part to remember about throwing errors is that the execution of the current scope exits. This is easy to think about for functions if you think of it as just a call to return. Any code after the throw will not be executed. It is a little less intuitive within do-catch blocks. A do-catch can have multiple calls to functions that may throw errors, but as soon as a function throws an error, the execution will jump to the first catch block that matches the error:
do { try function1() try function2() try function3() } catch { print("Error") }
Here, if function1
throws an error, function2
and function3
will not be called. If function1
does not throw but function2
does, then only function3
will not be called. Also note that we can prevent that skipping behavior using either of the two other try
keywords:
do { try! function1() try? function2() try function3() } catch { print("Error") }
Now if function1
throws an error, the whole program will crash and if function2
throws an error, it will just continue right on with executing function3
.
Now, as I hinted before, there will be circumstances where we need to perform some action before exiting a function or method regardless of if we throw an error or not. You could potentially put that functionality into a function which is called before throwing each error, but Swift provides a better way called a defer block. A defer block simply allows you to give some code to be run right before exiting the function or method. Let's take a look at an example of a personal chef type that must always clean up after attempting to cook some food:
struct PersonalChef { func clean() { print("Wash dishes") print("Clean counters") } func addIngredients() throws {} func bringToBoil() throws {} func removeFromHeat() throws {} func allowItToSit() throws {} func makeCrèmeBrûlée(URL: NSURL) throws { defer { self.clean() } try self.addIngredients() try self.bringToBoil() try self.removeFromHeat() try self.allowItToSit() } }
In the make crème brûlée method, we start out with a defer block that calls the clean method. This is not executed right away; it's executed immediately after an error is thrown or immediately before the method exits. This ensures that no matter how the making of the crème brûlée goes, the personal chef will still clean up after itself.
In fact, defer even works when returning from a function or method at any point:
struct Ingredient { let name: String } struct Pantry { private let ingredients: [Ingredient] func openDoor() {} func closeDoor() {} func getIngredientNamed(name: String) -> Ingredient? { self.openDoor() defer { self.closeDoor() } for ingredient in self.ingredients { if ingredient.name == name { return ingredient } } return nil } }
Here, we have defined a small ingredient type and a pantry type. The pantry has a list of ingredients and a method to help us get an ingredient out of it. When we go to get an ingredient, we first have to open the door, so we need to make sure that we close the door at the end, whether or not we find an ingredient. This is another perfect scenario for a defer block.
One last thing to be aware of with defer blocks is that you can define as many defer blocks as you like. Each defer block will be called in the reverse order to which they are defined. So, the most recent deferred block will be called first and the oldest deferred block will be called last. We can take a look at a simple example:
func multipleDefers() { defer { print("C") } defer { print("B") } defer { print("A") } } multipleDefers()
In this example, "A"
will be printed first because it was the last block to be deferred and "C"
will be printed last.
Ultimately, it is a great idea to use defer any time you perform some action that will require clean-up. You may not have any extra returns or throws when first implementing it, but it will make it much safer to make updates to your code later.