BUNDLE: Jumpstart your iOS development with the Swift Power Pack – now available! >>

The defer keyword in Swift 2: try/finally done right

If you already read my article discussing all the new features in Swift 2 but wanted to know more about the new defer keyword, this article is for you.

Please note: this article covers Swift 2, iOS 9 and Xcode 7. You must have installed Xcode 7 if you want to follow along.

How it used to be: messy returns

Here's some dummy Swift code that opens a file, writes some data, then closes the file:

func writeLog() {
    let file = openFile()

    let hardwareStatus = fetchHardwareStatus()
    guard hardwareStatus != "disaster" else { return }
    file.write(hardwareStatus)

    let softwareStatus = fetchSoftwareStatus()
    guard softwareStatus != "disaster" else { return }
    file.write(softwareStatus)

    let networkStatus = fetchNetworkStatus()
    guard neworkStatus != "disaster" else { return }
    file.write(networkStatus)

    closeFile(file)
}

I'm not going to try to fill in any code for the methods in there, they are hopefully self-explanatory. As you can see, a file is opened, then various types of data are written out, before finally the file is closed.

But what happens if any one of those status checks returns "disaster"? Answer: our guard condition will trap the error and exit the method – leaving the file open.

There were two solutions to this, neither of which were nice. The first was to copy and paste the call to closeFile() so that it is called before any of those returns. The second was to create a pyramid of doom, with several stacked conditional statements to handle writing.

Swift 2 solves this problem, but does so in a simple and clear way: the defer keyword.

Deferring work in Swift 2: defer

Swift 2 introduces the defer keyword, which effectively means "here's some what I want you to do later, no matter what." That work can be whatever you want: a single method call closing a file, or 50 lines of code doing some other important clean up work. The important thing is that Swift ensures that it will be run before the current scope is ended.

So, we could rewrite the above code like this:

func writeLog() {
    let file = openFile()
    defer { closeFile(file) }

    let hardwareStatus = fetchHardwareStatus()
    guard hardwareStatus != "disaster" else { return }
    file.write(hardwareStatus)

    let softwareStatus = fetchSoftwareStatus()
    guard softwareStatus != "disaster" else { return }
    file.write(softwareStatus)

    let networkStatus = fetchNetworkStatus()
    guard neworkStatus != "disaster" else { return }
    file.write(networkStatus)
}

With that defer call in place, closeFile() will be called no matter which of the guards are triggered, or even if none of them trigger and the method completes normally.

That's what I mean when I say that deferred work will always take place: if you return from a method at the end or part way through, or if you exit a method by throwing an error, your deferred work will take place.

This makes it perfect for ensuring code is cleaned up under all conditions, and is similar to try/finally in other languages.

Defer scope in Swift 2

Using defer inside a method means that its work will be executed as the method is exiting. For example:

override func viewDidLoad() {
    super.viewDidLoad()

    print("Step 1")
    myFunc()
    print("Step 5")
}

func myFunc() {
    print("Step 2")
    defer { print("Step 3") }
    print("Step 4")
}

That will print "Step 1", "Step 2", "Step 4", "Step 3", "Step 5" – steps 3 and 4 are switched because 3 is deferred until the myFunc() method ends, i.e. when it goes out of scope programmatically.

This scope is effectively anything in braces, { and }, but realistically there are two main ways you may want to use it: inside a do block and inside a loop.

do blocks are usually used with catch to handle errors smoothly, but you don't need that – you can use do to create a temporary scope anywhere inside your method. Any variables created inside the do block exist only there, and any work you defer inside the block will execute as soon as the closing brace of the block is reached.

For example:

print("Step 1")

do {
    defer { print("Step 2") }
    print("Step 3")
    print("Step 4")
}

print("Step 5")

When that runs, you'll see 1, 3, 4, 2, 5 because 2 is deferred until the end of the do block.

As I said, loops are also natural places you might want to use defer, and it will execute at the end of each loop iteration. For example:

for i in 1...10 {
    print ("In \(i)")
    defer { print ("Deferred \(i)") }
    print ("Out \(i)")
}

You can defer multiple things

One of the most powerful features of defer is that you can stack up multiple deferred pieces of work, and Swift will ensure they all get executed. What's more, it executes them in reverse order, meaning that the most recently deferred thing gets run first – effectively unwinding a stack.

Not only does this mean you can defer work without having to worry about what if anything was already deferred, but also that Swift safely unwinds its defer stack based on the order you chose.

There is one catch, albeit a minor one: your defer calls shouldn't try to exit the current scope using something like a return call or throwing an error. Other than that, you're good to go!

Test your Swift today

Think you know Swift? My book Swift Coding Challenges will put your Swift skills to the test – it's perfect for job interviews and more!

Click here to visit the Hacking with Swift store >>