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

Escape

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:

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:

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:
Compiler error - Closure use of non-escaping parameter 'completion' may allow it to escape

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!

  • Daniel Bergquist

    Ok, so I now understand what escaping closures are and how to add the appropriate attribute. Now my question is why does the compiler require them to be attributed?

    • Jared Stefanowicz

      Probably so it knows to keep the closure in memory instead of popping the function off the stack when it returns and the closure argument along with it

      • Daniel may be wondering the same as another reader: “It seems that the Swift compiler can figure it out already (due to the error message), so having to specify it seems redundant.”

        Makes sense – if the compiler’s so smart as to tell you that @escaping needs to be there, why is it forcing us to specify our intent explicitly?

        Hmm… Could it be sort of like error throwing, and needing to specify that error-throwing ability with the throws keyword? Thinking out loud…

    • THAT – is a great question that I’m not 100% sure of the answer on. Another reader on Medium asked the same, and I was going to go poke around in the GitHub repo for some insight.

      If I find something enlightening, I’ll let you know, or write a follow-up article to this one and ping you on it!

    • Kateri Ze

      Could it be that the @escaping is needed to ignore the body of the function and to come back to it when it is actually called?

      • Daniel Bergquist

        Do you mean in a similar manner to how the await keyword is used in C#? That is exit the current function while we wait for an async task to complete and then resume the function where execution left off after the async task completes?

        If so, then no, Swift does not have that feature.

    • Hey Daniel – I finally had time to do some digging and I came across some info in the Swift Evolution mailing list that might shed some light on the “why” question. I wrote about it here: https://www.andrewcbancroft.com/2017/05/11/why-do-we-need-to-annotate-escaping-closures-in-swift/

      • Daniel Bergquist

        Thank you for the update.