I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
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:
- Call
doSomething
and pass it a closure of Type() -> Void
doSomething
performs its work and returns- 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:
1var completionHandlers: [() -> Void] = []
2
3func doSomething(completion: () -> Void) {
4 completionHandlers.append(completion)
5}
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:
1func doSomething(completion: () -> Void) {
2 doSomeOtherAsynchronousThing(completion: {
3 () -> Void in
4 // code that executes after the other asynchronous thing is done
5 completion()
6 })
7}
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!