Understanding memory leaks in closures
Ahh memory leaks… At first you don’t even know they can exist, then you ignore them, and then you start seeing them everywhere without knowing how to handle them properly.
OK, so maybe now is the time to understand clearly, when they can happen and what tools you can use to get rid of them.
Apple made a great article about strong reference cycles in classes. It’s easy to understand what is a memory leak and how to avoid them in this case. However, this is a pretty rare scenario, and quite easy to spot. I find the part about closures a lot more confusing. So let’s clarify this once and for all.
Reference cycles with closures
First, you have to understand what a closure is and what it does. I like to picture it as a piece of code which, when declared, creates its own temporary class, that contains a reference to all the objects it needs in order to execute itself.
Let’s take a simple example to start with: A ViewController that has a CustomView. That CustomView has a closure that is called when a button is tapped.
When we give a value to the closure, it needs to keep a reference to some variables in order to execute itself. Here, it needs
timesTapped. They’re the only external values it needs; and to keep those values available, it will create a strong reference to them. That will prevent those values from being freed, and the closure from crashing in case they were deallocated.
But wait, ViewController has a strong reference to CustomView, that has strong reference to the onTap closure that just created a strong reference to self. So here’s what we have:
As you can see pretty clearly we have a cycle. Meaning that, if you exit this view controller, it can’t be removed from memory because it’s still referenced by the closure.
This example is pretty clear, our
viewController has a property
subview, that has a property
onTap which captures
self . But unfortunately, it can become a lot more complicated.
Potential cycle examples
The question you always have to ask yourself is: Who owns the closure?
If you’ve ever built an iOS app you have had to deal with a UITableView at some point, and chances are, you also had to deal with custom cells with a custom button.
Here’s one way to do it in swift, first you have a CustomCell that has an action closure that allows you to define what happens when the button is tapped.
Then in your ViewController, you define the action you want to have for this button.
Who owns the closure? In here, it’s pretty clear, since we declare it explicitly in CustomCell. But, the cell belongs to the tableView which belongs to the tableViewController.
Like previously there’s a cycle, but this one is harder to spot if you’ve never seen it before:
You’ve certainly dealt with Grand Central Dispatch before, can you spot if there’s any cycle?
First, who owns the closure? The ViewController doesn’t have any property that references it. It is called on a DispatchQueue singleton, so worst case scenario, within the call
asyncAfter, the singleton keeps a reference to it. Unfortunately we cannot see what is the implementation. However, that closure will be executed only once, at a predefined time, so it doesn’t make sense for the singleton to keep a reference to it. In this case, when the closure is executed it will drop its reference to
self and since
self doesn’t reference the closure, there’s no cycle.
Note that the same logic can be applied to the UIView animations closures.
Let’s say you have an app with a LoginViewController and you are using Alamofire to get a response from your backend:
Who owns the closure? The closure is declared as a parameter of a function of
request. But you don’t really know what Alamofire will do with that closure nor when it is released.
If you take a look at the implementation, you can see that the
request has an OperationQueue:
queue. When calling the function
response() we pass the closure that is then added to the
queue. When the closure finishes its execution, it is simply removed from the
queue. So there’s is no cycle in here because only the
queue retains the closure but it’s released once executed.
Note that, even if you keep a reference to the
request or retain a SessionManager, the closure will be released and you won’t have any cycle.
In this example, you have an UISearchBar, and whenever you change the text in the
searchBar you would like to update a label.
Who owns the closure? The closure can be called many times and we don’t don’t know when, so RxSwift needs to keep a reference to it. In this case the closure is actually owned indirectly by
searchBar. It makes sense because the closure is released when
searchBar is. But wait,
searchBar is owned by
self when it’s added to the view hierarchy, and the closure references
self. So in this case we have a cycle, and we need to break it in order to avoid a memory leak.
To break a cycle, you just need to break one reference, and you will want to break the easiest one. When dealing with a closure you will always want to break the last link, which is what the closure references.
To do so, you need to specify when capturing a variable that you don’t want a strong link. The two options that you have are:
unowned and you declare it at the very beginning of the closure.
In the UITableView example it would look like this:
Whether to use
unowned, can be a bit tricky, generally you want to use
unowned if the closure cannot exist longer than the objects it captures. In this scenario, the cell and the closure cannot live longer than the
tableViewController so we can use
unowned. If you would like to know more about
unowned I would recommend to read these great articles.
Debugging memory leaks
Since it is hard sometimes to know if your closure is kept as a reference, especially when you’re using third party libraries or private implementation, you will need to debug to find the cycles. Xcode provides a new tool that is really handy to help you find leaks. Navigate through your app and click on the little graph icon at the bottom of Xcode to check what’s in memory.
In the TableView example, if you don’t put
unowned in the
onButtonTapclosure you will see something like:
The exclamation mark on the right indicates a leak. But Xcode sometimes has troubles detecting the leaks. It is entirely possible that you have a leak but no exclamation mark. In that case you just need to pay attention to what is in memory and if you see something that shouldn’t be there you most likely have a leak.
Hope you enjoyed this article and that it gave you a better understanding of how memory leaks can happen in closures. Feel free to comment if you have any question or feedback!
A special thanks to Rémy Virin who helped me write that article with his deep understanding of the topic.