Closures and Memory

Closures can create memory traps if you’re not careful. In this section, we’ll look at memory management for closures and explore how leaks can happen while working with them.

In Swift, functions and closures are first-class citizens types. We can create them as variables, pass them as parameters, and return them from functions.

Swift is a safe language, and by default it will never let us access uninitialized memory—it really works hard to achieve this. In particular, when we make a closure, it makes sure that all the variables we access in the body of the closure are alive. This means it will bump the reference count for any object that’s accessed.

The count will only decrement when the closure itself is de-allocated.

Let’s see this in action. In the following example, we have a counter closure which increments the global variable tickCount by one.

This closure has safe access to the variable tickCounter because of tickCounter ’s global scope. If tickCounter was an object, using it inside the closure would bump up its reference count so that the object stays alive until the closure stays alive.

Problems start occurring when one of the objects accessed inside the closure have a reference back to the closure. This situation can be represented like this:

Retain cycle in closures

The closure is referring three objects within its scope and strongly holds them. Things are fine until then, but the problem starts occurring when one or more of these three objects starts referring back to the closure (strong reference), as we can see in the image.

It might be possible that Object 3 has created the closure and owns it. This owning relationship gives rise to a retain/reference cycle.

The solution to the problem mentioned above is to break the strong reference cycle by altering how we access Object 3 inside the closure and making that reference as either weak or unowned as shown in the image below:

Solution to closure retain cycle

The block of code shown below provides an example of a leak:

If you run this code in a playground, you’ll find that the result of execution never results in de-initialization. That happens because we have a strong reference cycle in our growOld closure that gets initialized lazily to a closure that increments the age by some amount. The code captures self inside a closure, and the memory is never vacated. You’ll see such behavior inside big iOS ViewControllers that have closure properties.