What in the World is an “Escaping Closure” in Swift?

If you’re mostly in the business of coding up closures to pass off to other functions as callbacks, you may not have run into the concept of an “escaping closure” yet.

When you step out of the role of consuming other peoples’ APIs in to the realm of creating your own (and you do this all the time!), this is where you’ll likely run into the concept of an “escaping closure” in certain scenarios.

I want to start off by defining the term. Then I’ll throw out a couple of usage scenarios that cause us to need to think in terms of a closure “escaping”.

Definition

First, a definition, shall we?

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.
 –
Apple Developer Documentation

So apparently, you can get yourself into the situation where you’re designing a function that takes in a closure as one of its parameters:

func doSomething(completion: () -> Void) { ... }

Furthermore, it appears that it’s possible to find yourself in a situation where the closure will execute, but somehow, it doesn’t get executed until after the function it got passed into returns. So it would go something like this:

  1. Call doSomething and pass it a closure of Type () -> Void
  2. doSomething performs its work and returns
  3. The closure you passed (the one of Type () -> Void) gets executed

Weird, huh? How in the world can that happen? I’ll talk about that in a second.

The point for now is this: whenever you’re in a situation like this where the closure that you pass to a function gets executed after the function you passed it to returns, you’ve got an “escaping closure” on your hands.

As an API consumer, you might not know or care about the escap-y-ness of the closure.

As an API designer (which again, could be yourself, if you’re the one writing the definition of doSomething(completion:)), you have to care, because the Swift compiler will be angry with errors if you don’t.

So how do “escaping closure” scenarios happen?

Escaping closure scenarios

Here are a few scenarios that give rise to escaping closures.

Storing the closure as state

Apple’s docs give an example of appending a closure that’s passed into a function to a mutable array of closures within your class/struct:

var completionHandlers: [() -> Void] = []
func doSomething(completion: () -> Void){
completionHandlers.append(completion)
}

Presumabley then, at some later time after doSomething returns, all of the completion handlers in the array will be looped over and executed (or something like that)…

As you can see, this follows the 1. Pass closure, 2. doSomething returns, 3. Closure executed pattern we had before, doesn’t it?

So this is one scenario that could give rise to an escaping closure, IF you designed your system this way.

Whenever you take the closure, store it as state, and then execute it at a later time, the closure is “escaping” the function it got passed into.

Asynchronous asynchronous callbacks

No, I didn’t get repetitively redundant there. Well… I did, but it was on purpose. :]

Supposing that you’re working on your doSomething(completion:) function.

Within it, you make a call to another function that performs an asynchronous action and asks for a completion closure of its own.

What if you only want to call the completion handler that was passed into doSomething after the asynchronous action of the other function completes. That is, what if you only want the two completion handlers to be executed together:

func doSomething(completion: () -> Void) {
doSomeOtherAsynchronousThing(completion: {
() -> Void in
// code that executes after the other asynchronous thing is done
completion()
})
}

Here, you’ve got this nested asynchronous behavior going on, don’t you? Asynchronous asynchrony is happening.

Whenever you defer the execution of a closure to a time that’s after the “parent” function returns, you’ve got an “escaping closure” on your hands.

Declaring “this is an escaping closure!” in code

Whenever you’re implementing a function that introduces the possibility for a closure passed to it to escape, you’ll know it.

The Swift compiler will complain, and your app won’t build:

What do you do to fix it?

It’s pretty simple. In the declaration line of your function, you need to add the @escaping attribute right before the closure’s Type declaration:

doSomething(completion: @escaping () -> Void)

Wrapping up

My goal was to shed some light on the concept of “escaping closures”. With the definition and the example scenarios that give rise to escaping closures, my hope is that things are a little more clear for you. Sound off in the comments if you’re still struggling, or if you’ve run across other scenarios requiring you to use the @escaping attribute!


Originally published at www.andrewcbancroft.com on April 26, 2017.


Andrew Bancroft is the author of multiple iOS/Swift-related courses on Pluralsight. He blogs regularly at www.andrewcbancroft.com, is passionate about learning and discovery, and enjoys sharing new insights with others. Andrew tweets about software development as @andrewcbancroft.