What the **** is an @escaping closure in Swift
It's a fundamental question in every Apple Developer tech interview. What is the main difference between closures marked with @escaping and non-escaping closures?
In this article, I'll go over the fundamentals of this question so you can better understand the how and why of using @escaping.
Closures
Before diving into the important part, it's important to reflect on what is a closure. According to the official Swift language book: The Swift Programming Language (5.7 edition), is as follows [1]:
Closures are self-contained blocks of functionality that can be passed around your code. (...)
Closures can capture and store references to any constants and variables from the context in which they are defined.
But aren't functions the same thing? Take a look at this example:
According to Swift's official documentation functions are a special case of closures [1].
And closures can take one of three forms [1]:
- Global functions that have a name and don't capture any values.
An example of a global function is theprint(_:separator:terminator:)
since it's not tied to any class or structure. - Nested functions that have a name and can capture values from their enclosing function.
- Closure expressions are written in a lightweight syntax that can capture values from their surrounding context.
Here's an example, can you spot the closure here?
Syntax
Now that we know more about closures, what is their usual syntax? According to the official documentation is as follows [2]:
{(parameters) -> return type in
statements
}
The parameters can be in-out but can't have a default value (similar to what you use in functions -> name: String = "test") [2].
Trailing Closures
If you're using a closure as the last argument in a function call you can use it as a trailing closure instead.
Instead of having the closure as a separate parameter (like in example 3 of closure types), you can write it after the function call. So the example that was used before can be simplified to:
Escaping Closures
Now that we explored the concept of closures and how they intertwine with functions and other elements it's time to go over what is an escaping closure.
According to Apple's official documentation:
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. [3]
It's common to see during iOS implementations, the @escaping before adding a closure inside of a function. But why do we need to add it?
Since I'm sure you're tired of seeing code, I want to bring a new example with simpler terms. According to what we saw before a closure is a block of instructions that can be passed around.
Let's pretend that instead of closures people pass around paper notes 📑. Imagine that there is a person who owns a box of notes that people can add notes to.
After everyone added their notes to the box someone else may want to read all of the notes inside. Since every note (or closure) was @escaping the notes can be read after the function returns. So every note in this case can be read.
Now you may ask, what would be the outcome of this example if all of the notes were non-escaping?
For starters, the owner of the box of notes wouldn't have any notes in it because after the function adds something to the box returns, the completion ceases to exist.
When another person asks to read all of the notes inside of the box, since the box doesn't hold any notes there is nothing to read.
This is the main difference to know when to use the @escaping.
Conclusion
Closures are very helpful when you want to pass around a block of code that can be used by another component. However, it should be up to the developer to exercise when to use the escaping annotation. If this closure should live/exist after the function returns, then it should be escaping.
Glossary
If you're unfamiliar with some of the words in this article I created a simple glossary to simplify each concept.
- Closures: Blocks where you can pass around code from one component to another, this can be between classes, methods, or anything.
- Functions: Case of a closure, self-contained code that performs a specific task [4].
- Nested Function: Function inside of another function. Nested functions have access to variables that were declared in the outer function. This helps organize the code to make it shorter and simpler to read [5].
- Global Functions: Functions that are defined at a global scope [4]. They are declared outside of any class or structure, you can call it from anywhere in your code.
References
- Closures: The Swift Programming Language (Swift 5.7) page 160
- Closures - Closure Expression Syntax: The Swift Programming Language (Swift 5.7) page 162
- Closures - Escaping Closures The Swift Programming Language (Swift 5.7) page 172
- Functions: The Swift Programming Language (Swift 5.7) page 142 - 158
- A Swift Tour: The Swift Programming Language (Swift 5.7) page 11
Thank you for reading this article. Your appreciation is deeply valued. If you're inclined to support my work further, I would greatly appreciate it if you could follow me on social media platforms like LinkedIn, Twitter, and Github.
Nonetheless, it brings me immense satisfaction to offer free educational content that is accessible to all. Have a great day! ☀️