Escaping Closures in Swift

Updated on August 2nd, 2021

⏱ Reading Time: 6 mins

Swift is a really powerful programming language, and the number one choice when it comes to make applications for the Apple ecosystem; iOS, macOS, watchOS and tvOS. And as developers who write code in Swift, we make use of closures a lot; a big and important chapter of the language.

Closures is not a topic that a beginner starts with. However, it’s something that everyone has to learn about soon enough. There are various aspects to walk through and understand how they work. Among all those there is a particular one; escaping closures and the @escaping attribute. In this post I will explain what they are and what collateral effects they might bring in an as simple as possible way.

Escaping vs non-escaping closures

When talking about escaping closures, we always refer to closures that we provide as arguments to functions or methods. Generally speaking, we distinguish closures we provide to methods (or functions) in two categories:

  1. Closures that are called before the execution of the method has finished.
  2. Closures that are called after the execution of the method has finished.

In the latter case we are talking about escaping closures; closures that continue to live even after the execution of the method, until we call them at any time later in the future.

In the former case, which is the exact opposite from what I described above, we call closures as non-escaping.

Up until Swift 3, all closures passed to methods or functions as arguments were considered as escaping by default. This is no longer true since Swift 3; all methods are considered to be non-escaping by default, meaning that they are invoked before the execution of the method is finished.

The following part of code demonstrates a non-escaping closure:

The completion closure is called before code execution leaves the method, so this is not a case of an escaping closure.

However, how does a closure escape from a method so we end up with the opposite of the above case?

Escaping from the method

In order to make a closure an escaping one, it’s necessary to keep a reference to it out of the method’s scope, so we can use it at a later time. Take a look at the following code:

Here we have the result property that holds the result of the addition that takes place inside the method. But we also have the resultHandler property; this one keeps a reference to the completion closure provided as argument to the method.

The closure escapes from the method with the following line:

However, that’s not the only required action so we can tell that completion is an escaping closure. We have to explicitly indicate that to compiler, otherwise we’ll see the following error in Xcode:

Assigning non-escaping parameter Xcode error

To fix it, we need to mark the closure with the @escaping attribute. We place this attribute after the closure’s name and the semicolon, but before the closure’s type, like so:

The compiler is no more complaining, and completion is now officially an escaping closure.

Putting escaping closure in action

Let’s add two more methods in the above Demo class; one that will be calling the add2(num1:num2:completion:) method, and one that will be calling the resultHandler closure in order to get the final result:

The first method doubles the result that the add method calculates. However, that result won’t be doubled and the code inside the body of the closure in the add2(num1:num2:completion:) method won’t be executed until we call the getResult() method.

This is where we benefit from escaping closures; we can trigger the invocation of the closure whenever that’s necessary in our code and when the timing is proper. And even though the provided example is purposely too simple, actual advantages become more apparent and bolder in real projects.

Watch out for strong reference cycles

Let’s make one last addition to the Demo class, and let’s implement the default initializer and deinitializer methods:

init() is the first method called when a Demo instance is initialized, and deinit the last one before the instance is being released. I added a print command to both of them in order to verify that they are called, and that there is no memory leak when using the method with the escaping closure above.

Note: A memory leak can exist when we try to release an object by making it nil, but the object still remains in memory because there are strong references between that and other objects that keep it alive.

Now, let’s add the following lines that make use of all the above:

At first, we initialize an optional instance of the Demo class, so we can make it nil later. Then, we call the doubleSum(num1:num2:) method in order to add the two numbers given as arguments, and then double that result. However, and as I already said earlier, this won’t happen until we call the getResult() method; the one that actually calls the escaping closure in the add2(num1:num2:completion:) method.

Finally, we print the value of the result property in the demo instance, and we make demo nil.

Note: Force unwrapping optional values with the exclamation mark (!) as shown in the above code snippet is a really bad practice, and please don’t do that. The only reason I’m doing it here is for keeping things as simple as possible.

The above lines will print the following:

Notice that the “Deinit” message is missing here! That means that the deinit method is not called, and it proves that making the demo instance nil has no actual result. As it seems, with just a few simple lines of code we managed to have a memory leak.

The reason behind the memory leak

Before we find a way to fix that memory leak, it’s necessary to understand why it happened. In order to find it out, let’s make a few steps back to revise what we previously did.

First of all, we made the completion closure escape from the method with the following line:

This line is more “guilty” than it seems, because it creates a strong reference to the completion closure.

Note: Closures are reference types, just like classes are.

However, that alone is not enough to create a problem, as by releasing the demo instance would remove the reference to the closure. The actual trouble begins in the closure’s body inside the doubleSum(num1:num2:) method.

There, we create another strong reference from the closure to the demo instance this time, by capturing the self object when using it to access the result property:

When having both of them in place, the Demo instance keeps a strong reference to the closure, and the closure a strong reference to the instance. That creates a retain cycle, also known as strong reference cycle. When that happens, each reference type keeps the other alive in memory, so none of them is eventually being released.

Note though that this occurs only with classes that contain methods with escaping closures. Things are different with structs, as they are not reference but value types, and explicitly referring to self is not mandatory.

Eliminating the strong reference cycle

There are two ways to avoid the strong reference cycle, and therefore the memory leak. The first one is to manually and explicitly release the reference to the closure once we call it:

The second way is by capturing the self instance weakly in the closure’s body:

See the [weak self] in addition after the closure’s opening. With that, we establish a weak reference to the Demo instance, and therefore we avoid the retain cycle. Notice that we use self as an optional value, suffixing it with the question mark (?) symbol.

It’s not necessary to apply both changes in order to avoid the strong reference cycle. Regardless of which one we will eventually choose, the output will be containing the “Deinit” message from now on as well. That means that the demo object is becoming nil, and we have no more a memory leak.

Summary

There is one thing to take with you leaving here, and that is to be cautious when using escaping closures. No matter whether you implement your own methods that accept escaping closures as arguments, or you are making use of APIs that have escaping closures, always make sure that you don’t end up with strong reference cycles. Memory leaks is a big “no” when developing applications, and we surely don’t want our apps to crash at some point or to be terminated by the system because of that. On the other hand, don’t feel hesitant in using escaping closures; they offer advantages that can lead to more powerful code.

In any case, I tried to present this topic as simply and understandable as possible, which I hope you enjoyed and you found a couple of new things to learn in it. Thank you for reading!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.