Escaping Closures in Swift Explained
A closure is said to “escape” a function when it’s called after that function returns. In Swift, closures are non-escaping by default. What are escaping closures, anyway? And what do you use @escaping
for? Let’s find out!
This tutorial discusses closures, escaping vs. non-escaping, capture lists and retain cycles, and what that means for practical iOS development.
What we’ll get into:
- What escaping closures are, and why you need them
- How and when to use the
@escaping
keyword - Risks of escaping closures, such as strong reference cycles
- Fixing a strong reference cycle in an escaping closure
Enjoy!
- Quick Recap: What’s a Closure?
- Escaping Closures in Swift
- Why Use @Escaping?
- Strong Reference Cycles in Escaping Closures
- Wrapping Up
Quick Recap: What’s a Closure?
Closures are awesome! You probably already know this, but a closure is a block of code you can pass around and use elsewhere in your code.
It’s kinda like a function you can assign to a variable. You can move the closure around in your code, and call it at a later point. This makes closures exceptionally useful as completion handlers, i.e. the code you call when a long-running task has finished.
Check this out:
let makeGreeting: (String) -> String = { name in
return "Hello, \(name)!"
}
let message = makeGreeting("Bob")
print(message)
// Output: Hello, Bob!
In the example above the constant makeGreeting
contains a closure. It has one parameter of type String
, and it’s returning a value of String
as well. It works the same way as a function.
When the closure is called in the above example, with makeGreeting("Bob")
, its value is assigned to constant message
and printed out.
A common use-case for a closure is a so-called completion handler. Say you’re downloading an image from the web that you want to display in a view controller. Your code could look something like this:
let task = URLSession.shared.dataTask(with: URL(string: "http://example.com/cats.jpg")!, completionHandler: { data, response, error in
// Do something with image data...
})
In the above code, we’re starting a data task with URLSession; making a HTTP request to download some cats.jpg file. The argument for the completionHandler
parameter of the dataTask()
function is a closure. It’s called when the image has been downloaded, hence the name completion handler.
Closures are powerful. In the example above, we’re writing the response to the data task in the same “place” in the code as the initial request. That means we can start the request and handle it, as if both actions follow each other immediately.
There’s a bit of time between the start of the request and its completion, but we can code that as if it’s immediate. That makes reading the code and reasoning about it much easier!
The code between the squiggly brackets { ··· }
is passed into the dataTask(with:completionHandler:)
function. Passing around closures is a core principle, and it’s what escaping closures are about, too. Closures that outlive the context that declared them can potentially cause a memory leak, as you’ll soon see!
Good to know: Closures are reference types. Pass ’em into a function and you’re sharing a reference to the closure; it’s not copied.
Escaping Closures in Swift
Let’s take a look at the declaration of that dataTask(with:completionHandler:)
function, as found in the Swift Foundation framework’s source code:
func dataTask(with url: URL,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
Right there, you can see that this function has 2 parameters:
-
url
of typeURL
– the URL resource we’re going to download/request -
completionHandler
– a closure
You can also see that the closure itself has 3 parameters, and returns Void
.
-
Data?
– the data that the request itself returns (i.e., the image) -
URLResponse?
– an object that contains information about the response -
Error?
– an optional value that contains an error object ornil
The function declaration also contains a special keyword: @escaping
. This means that the closure that’s passed in as an argument for this parameter can escape the dataTask(with:completionHandler:)
function. In other words, the closure is called after the function dataTask(···)
finishes executing.
Here’s the official definition of an escaping closure:
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.
It can be helpful to think of an escaping closure as code that outlives the function it was passed into. Think of that function as a jail, and the closure as a bandit who’s escaping the jail.
An example of an escaping closure is the completion handler. Depending on its implementation, a completion handler is typically executed in the future. A length task completes long after it was initiated, which means that the closure escapes and outlives the function it was created in.
If we’ve got escaping closures, we can also have non-escaping closures:
- An escaping closure is a closure that’s called after the function it was passed to returns. In other words, it outlives the function it was passed to.
- A non-escaping closure is a closure that’s called within the function it was passed into, i.e. before it returns. This closure never passes the bounds of the function it was passed into.
In Swift, a closure is non-escaping by default. If a closure can escape the function, you’ll need to annotate its function parameter with the @escaping
keyword. This is what happens in the code at the top of this section.
Here’s a quick code example of an escaping closure:
class TaskManager
{
var onTaskFinished:(() -> Void)?
func startLengthyTask(completionHandler: @escaping () -> Void)
{
// Store completion handler for later
onTaskFinished = completionHandler
// Do lengthy task
// ...
}
func onLengthyTaskFinished() {
onTaskFinished?() // Call completion handler
}
}
let task = TaskManager()
task.startLengthyTask(completionHandler: {
// Do this when task has finished...
})
What’s going on in the code? We’ve created a class called TaskManager
, with a function called startLengthyTask(completionHandler:)
. At the end of the code, we’re calling this function and pass a closure into it.
The closure for the completionHandler
parameter is passed into the function. Inside the function, the closure is assigned to the onTaskFinished
property of the TaskManager
class (of the same closure type). It’s temporarily stored there.
At some point in the future, the function onLengthyTaskFinished()
is called by the TaskManager
class. This will invoke the previously stored completion handler, which will execute the closure we passed into it.
First, note that the closure type for completionHandler
is marked with that @escaping
property. This is an escaping closure. But how does it escape? With this line:
onTaskFinished = completionHandler
The closure that’s passed into the function is now assigned to a property on the TaskManager
class. The closure will continue to exist, until after the startLengthyTask()
function returns.
Think of the closure as a number – you can use that number after the function exits, because it’s now assigned to that property. Beyond the startLengthyTask()
function, you cannot use its completionHandler
parameter.
But because its value – the closure – is stored in the onTaskFinished
property, you can use it later on. The closure has escaped the function it was passed into!
What’s all this talk about “passing into”? That’s technobabble for using a variable as input for a function. The variable – or its value, really – is passed into the function as an argument. Want to learn more? Dive into functions, closures, value vs. reference types, and scope/context.
Why Use @Escaping?
What’s the problem with escaping closures? The code we’ve used so far runs OK. Why would we need to mark a closure as @escaping
anyway?
In short, an escaping closure can cause a strong reference cycle if you use self
inside the closure. Safety features like non-default, explicit @escaping
are built into the Swift programming language, so you can remind yourself to check if you haven’t inadvertently caused a retain cycle.
The @escaping
keyword isn’t a feature, it’s a warning sign! You’re letting the closure escape anyway, and once Swift finds out, it’ll help you avoid problems by forcing you to mark the closure as @escaping
.
Before we move on, think back to the semantics of an “escaping” closure. In the previous example, you’ve seen that a closure can literally escape the function it was passed into by getting stored in a property outside that function.
When that escaping closure references self, or a strongly retained property, it will capture that reference strongly. If the escaping closure isn’t property released, you’ve created a strong reference cycle between self
and the closure.
Here’s a quick shorthand:
- A non-escaping closure can refer to
self
implicitly - An escaping closure must refer to
self
explicitly, or use a capture list
It’s impossible to create a strong reference cycle with a closure that’s non-escaping, because that closure doesn’t outlive the function it was passed into. The closure is released by the end of the function, and so are the values it captures.
It’s worth noting here that escaping closures work differently inside structs and classes. An escaping closure passed into a struct’s function cannot capture a mutable reference to self
, because structs are value types. You cannot change a struct outside (“escaping”) a function marked with mutating
; but only inside it.
It’s not that @escaping
avoids a strong reference cycle; it just makes your code more explicit. It increases the safety of your code, just as with optionals, which decreases the likelyhood of accidentally creating bugs.
You can find an example of a strong reference cycle in a closure, and how to fix that, in this tutorial: How To Fix Strong Reference Cycles in Swift
Strong Reference Cycles in Escaping Closures
Let’s take a look at a comprehensive example. We’re going to create an escaping closure that’ll reference self
and causes a strong reference cycle. We’ll also discuss 2 potential solutions.
First, some code:
class NetworkManager
{
var onFinishRequest: ((String) -> Void)?
func makeRequest(completionHandler: @escaping (String) -> Void)
{
onFinishRequest = completionHandler
// Do lengthy task...
self.finishRequest()
}
func finishRequest() {
onFinishRequest?("some data")
}
}
The NetworkManager
class is responsible for managing a lengthy task, like an HTTP request. You can best compare it with a bare-bones URLSession
, for example.
In the above code, you can see a function makeRequest()
. You can pass a closure into this function, which is called when a lengthy task completes. The closure escapes the function, because it’s assigned to the onFinishRequest
property. After the lengthy task completes, finishRequest()
will invoke the closure and pass some string data with it.
Next up, a class that uses the above code:
class API
{
var manager = NetworkManager()
var data = ""
init() {
print("init API")
}
func getData()
{
manager.makeRequest(completionHandler: { data in
self.data = data
print("called completion handler with data: \(data)")
})
}
deinit {
print("deinit API")
}
}
This API
class has a function getData()
, which calls the makeRequest()
function on the NetworkManager
object. It provides a closure for the completionHandler
parameter of that function.
We’ve also marked the init and deinit functions. Especially the deinit
function is important, because it’ll tell us if an instance of API
is deallocated. When a strong reference cycle occurs, the API
object won’t get deallocated.
What’s important to note here is that the closure strongly captures self
. This happens with the self.data = data
line of code. Why is that?
- In Swift, references are strong by default
- The
completionHandler
closure inmakeRequest()
is escaping - Closures will hold onto reference values you use inside them; this is called capturing or “closing over” (which is where closures get their name!)
Differently said, for the closure to hold onto self
, so it can assign the closure’s data
to the data
property of self
, a strong reference needs to be created. Otherwise, self
would be gone by the time the lengthy task finishes.
If you’re a bit unclear on ARC, strong references and closures, check out my 3-part series about them, starting with: Automatic Reference Counting (ARC) in Swift
Next up, we’re going to try out 3 scenarios:
- Run the above code and attempt to deallocate the
API
object - Run the above code, and make sure
self
is referenced weakly - Run the above code, and manually deallocate the closure
Let’s run the code, and see what happens. Here’s what we’re executing:
var api:API? = API()
api?.getData()
api = nil
// Output:
// init API
// called completion handler with data: some data
Would you look at that!? Even though we’re explicitly setting the api
variable to nil
, its deinit
function is not called and the API
object is not deallocated. This is a problem!
Let’s back up for a sec. What’s really going on here? Well, if we remove the call to getData()
, you get the following output:
init API
deinit API
This tells us that setting a variable to nil
will prompt the deallocation of its value. That’s how Automatic Reference Counting works; no one is holding onto the data, so it’s removed from memory to make space.
The culprit is getData()
, but why? Inside that function, we’ve got a closure that captures a reference to self
– the current class instance. A strong reference is created between self
and the closure. This closure is retained strongly when it’s assigned to the onFinishRequest
property.
Because the closure and self
hold strongly onto each other, they cannot get deallocated. This is a strong reference cycle, which causes a memory leak. Less memory for other apps. That’s bad!
This reference cycle typically resolves itself if the closure doesn’t escape, but ours does! When a non-escaping closure is passed into a function, it’s automatically deallocated by the end of that function. This resolves the strong reference.
OK, how do you fix this? You’ve got 2 options:
- Make the weak capture of
self
explicit with a capture list - Manually deallocate the closure when done, to break the cycle
First, the capture list. Check this out:
manager.makeRequest(completionHandler: { [weak self] data in
self?.data = data
···
})
The capture list [weak self]
creates a weak reference between the closure and self
. This prevents the strong reference cycle, because a weak reference doesn’t increase an object’s retain count. It does make the reference to self
of an optional type, so you’ll need to unwrap self
. (An alternative is unowned
.)
When we run the code with the above change, here’s the output you get:
init API
called completion handler with data: some data
deinit API
Awesome. Crisis averted!
An alternative is to manually deallocate the closure. The trick is to break the cycle, and you can do that at both ends. Check this out:
class NetworkManager
{
···
func finishRequest() {
onFinishRequest?("some data")
onFinishRequest = nil
}
}
We’ve changed the finishRequest()
function of the NetworkManager
class. Right after invoking the closure that’s stored in the onFinishRequest
property, we’re setting that property to nil
. This deallocates that closure, which releases any strong references (if any) that the closure holds.
Resolving the strong reference cycle isn’t always viable this way, because it depends on the implementation of the NetworkManager
code. It’ll need to hold onto that closure for some time, during which the strong reference persists.
Technically, making the onFinishRequest
property weak
would also break the strong reference cycle. It’ll also make the closure downright unusable, because the whole point is holding onto it until the lengthy task completes. You could also deallocate the NetworkManager
instance, or overwrite the onFinishRequest
property with something else. Of these alternatives, a capture list is the clearest and most reliable way to break the cycle.
Wrapping Up
In Swift, closures are non-escaping by default. This means that the closure can’t outlive the function it was passed into as a parameter. If you need to hold onto that closure after the function it was passed into returns, you’ll need to mark the closure with the keyword @escaping
.
Escaping closures have an inherent risk: strong reference cycles. This happens when the closure strongly holds onto a reference, like self
, and the closure itself is also retained. You can avoid a strong reference cycle by using a capture list, like [weak self]
.
Want to learn more? Check out these resources:
- Automatic Reference Counting (ARC) in Swift
- Weak vs. Strong References in Swift
- How To Fix Strong Reference Cycles in Swift
- The Ultimate Guide to Closures in Swift
- Initializers & init() Explained in Swift
- Scope & Context Explained In Swift
- Self and self in Swift
- How To: Promises in Swift
- How To Use Apple’s Developer Documentation For Fun And Profit
- How To Keep Up With Swift Changes