Closures and memory management

As we mentioned, a closure closes over the variables and constants it refers from the enclosing context. Now, if the closure is executed synchronously since the caller is blocked waiting for it to return, the closure may safely assume all variables and constants from the enclosing context still exist. On the contrary, when a closure is executed asynchronously, that is, without blocking the caller, there is no such guarantee, and it may happen that the closure is executed when the original enclosing context no longer exists. To ensure, then, that the variables and constants the closure uses outlive the enclosing context, Swift closures are able to capture those variables and constants:

import Dispatch

struct Book {
var title: String
var author: String
var price: Double
}

func sellBook(_ book: Book) -> () -> Double {
var totalSales = 0.0
func sell() -> Double {
totalSales += book.price
return totalSales
}
return sell
}

let bookA = Book(title: "BookA", author: "AuthorA", price: 10.0)
let bookB = Book(title: "BookB", author: "AuthorB", price: 13.0)

let sellBookA = sellBook(bookA)
let sellBookB = sellBook(bookB)

sellBookA() //-- book sales for bookA: 10
sellBookB() //-- book sales for bookA: 13

sellBookA() //-- book sales for bookA: 20
sellBookB() //-- book sales for bookA: 26

When you pass a closure into another function or method, you should pay attention to whether that closure is executed synchronously or asynchronously, and, in the latter case, add the @escaping modifier to the closure parameter declaration. For example, if you define a delay function that is meant to execute a function after some time, that is, after the delay function exits, then Swift will require you to mark the parameter that takes the closure with @escaping:

// If you remove @escaping here, the compiler will complain
func delay(_ d: Double, fn: @escaping () -> ()) {
DispatchQueue.global().asyncAfter(deadline: .now() + d) {
DispatchQueue.main.async {
fn()
}
}
}

For Swift, it is easy to realize the closure may escape the function scope, since you pass it to a dispatch queue. Still, it wants to make sure you know what you are doing and requires you to use @escaping.

Swift 3 introduced a fundamental change regarding escaping closures. While all closure parameters were previously escaping by default, since Swift 3 they are non-escaping by default. While breaking source code compatibility, this was a sensible change: non-escaping closure is less taxing on the runtime, since it can be allocated on the stack and can be optimized in more advanced ways. 

If you recall our discussion of automatic reference counting in Chapter 2Understanding ARC and Memory Management, it is easy to understand how capturing variables and constants works under the hood. In a simplified view of this process, when a closure captures a variable or constant, it automatically increments its reference count, thus ensuring it is not freed when the enclosing scope exits. After the closure is executed, the reference count of all captured entities is decremented, so they can be freed if need be. This makes capturing values in a closure as transparent and automatic a mechanism as possible, with just one exception: preventing retain cycles.

Cyclic, or circular, dependencies are a general concept in computer programming. A cyclic dependency is a relation between two or more entities that depend on each other, such as when entity A uses another entity, B, that in turns uses entity A. In Swift, due to the use of automatic reference counting, cyclic dependencies may also exist between objects in memory and are called retain cycles. In short, when a retain cycle ensues, ARC cannot free either of the objects. Usually, this issue is to be tackled by carefully deciding which of the two object "owns" the other, which gives a much more desirable unidirectional dependency.

To understand where retain cycles stem from, we may consider two typical scenarios where using a closure may cause a retain cycle:

In both scenarios, if the closure refers self in its body, then a retain cycle will ensue, since the reference to self from the closure body will make the closure capture self. We can say that the closure holds a strong reference to self.

This diagram depicts a typical case of a retain cycle: a closure retaining self

What happens in both cases is that the closure keeps object O alive by increasing its retain count. At the same time, since O owns the property where the closure is stored, O increases the closure's retain count. Thus, the two entities will indefinitely keep each other alive and your program will end up having a memory leak. Indeed, when O's owner is done with O and frees it, O's retain count is decreased; however, this is not sufficient to free the object O, since the closure L is still retaining it.

In case you are interested in looking at the relationship between retaining cycles and closures at a lower level, you might well ask yourself how a closure is affected by being stored into a variable or being used as an observer. The answer is in its simplest form; that is, when you define a closure as a callback to be executed asynchronously or as an inner function, a closure can just be allocated on the stack. This means it is not reference counted and its lifetime is strictly bound to that of the function or method declaring it. On the contrary, when we try to store a closure into a class property or pass it outside the class, the Swift compiler has to copy it over on to the heap, thus making it into a reference counted object.

So, now we know how retain cycles ensue within closures, your question may be: how can we avoid them? The answer is simple: by breaking the cycle. Let's consider the following code:

class OperationJuggler {

private var operations : [() -> Void] = []
var delay = 1.0

var name = ""

init(name: String) {
self.name = name
}

func addOperation(op: (@escaping ()->Void)) {
self.operations.append { [weak self] () in
if let sself = self {
DispatchQueue.global().asyncAfter(deadline: .now() + sself.delay) {
print("Delay: " + String(sself.delay))
op()
}
}
}
}

func runLastOperation(index: Int) {
self.operations.last!()
}
}

Here, we have an OperationJuggler class that manages a list of operations through an operations array. Operations are just functions or closures that, for the sake of this example, take no arguments and have no return value. This code demonstrates the pattern we have described already: OperationJuggler stores the closure created by addOperation in its operations property. The closure refers self, so it holds a strong reference to it, but the operations array, which is strongly referenced by self, also takes a strong reference to the closure. Unfortunately, the preceding code will compile just fine. Even worse, Xcode Analyze command is not able to detect the cycle. Therefore, you will end up knowing that, at runtime, your app may crash due to its memory footprint.

It is not hard to show that the preceding code indeed creates retain cycles that prevent OperationJuggler instances from being freed when you are done with them. For example, we could use OperationJuggler in a minimal ViewController, like in the following snippet:

class ViewController: UIViewController {

var opJ = OperationJuggler(name: "first")

override func viewDidLoad() {
super.viewDidLoad()

opJ.addOperation {
print("Executing operation 1")
}

self.opJ.runLastOperation(index: 0)

//-- this will release the previous OperationJuggler
self.opJ = OperationJuggler(name: "replacement")
}
}

In viewDidLoad, we added an operation to our OperationJuggler, stored in the opJ property; then, after one second, we overwrote the OperationJuggler in opJ with a new instance. We would expect that the original instance is freed when we do it, but this is not the case. You can easily convince yourself of this  by adding a deinit method to OperationJuggler and checking that it is not ever called:

class OperationJuggler {

...

deinit {
print("OperationJuggler freed")
}
}

So, how can we remove this retain cycle? Swift has a special syntax for this that you can use to break the cycle, as shown in the following rewrite of OperationJuggler:

class OperationJuggler {

private var operations : [() -> Void] = []
var delay = 1.0

var name = ""

init(name: String) {
self.name = name
}

func addOperation(op: (@escaping ()->Void)) {
self.operations.append { () in
DispatchQueue.global().asyncAfter(deadline: .now() + self.delay) {
op()
}
}
}

func runLastOperation(index: Int) {
self.operations.last!()
}
deinit {
self.delay = -1.0
print("Juggler named " + self.name + " DEINITIED")
}

}

As you can see, a new [weak self] attribute has made its appearance in the closure body. This will prevent the closure from retaining self, thus not creating the retain cycle. As a consequence of that, it cannot be taken for granted that the closure, to which the self object refers, still exists when it is executed, as it may have been freed. In this case, in accordance with weak ownership, self will be nil. In other words, self is an option type in the closure body, and we need to qualify its use with ?, !, or a guard. In the preceding example, we opted for using the forced unwrapping ! operator, since we are not really interested in preventing our program from crashing, but, in production code, you should be more vigilant. For example, we could write our closure like that shown in the following snippet:

 func addOperation(op: (@escaping ()->Void)) {
self.operations.append { [weak self] () in
DispatchQueue.global().asyncAfter(deadline: .now() +
(self?.delay ?? 1.0)) {
op() }
}
}

But more generally, we should prefer a guard:

    func addOperation(op: (@escaping ()->Void)) {
self.operations.append { [weak self] () in
guard let self = self else { return }
DispatchQueue.global().asyncAfter(deadline: .now() + self.delay) {
op()
}
}
}
The guard let self = self else { return } syntax was introduced in Swift 4.2, so do not try to use it in previous versions of the language. If you are using an older compiler, you can use a somewhat similar syntax, guard let weakSelf = self else { return }, which unfortunately forces you to create a new name to safely refer to self.

The fact that OperationJuggler manages operations that are closures is just a way to make our example richer, but the retain cycle would ensue just the same if it handled integers, as in the following code:

class IncrementJuggler {

private var incrementedValues : [(Int) -> Int] = []
var baseValue = 100

func addValue(increment: Int) {
self.incrementedValues.append { (increment) -> Int in
return self.baseValue + increment
}
}

func runOperation(index: Int) {
//...
}
}

Admittedly, retain cycles do not ensue only in the two scenarios mentioned here, nor are they always as easy to spot as in our examples. For example, you will also get a retain cycle when you store a closure in an object, O', which is owned by O, as it happens when you register a closure from your view controller with the view model that the view controller owns. As a general rule of thumb, you should pay special attention every time you are using a closure that refers to self, although it would be overkill to handle all such closure as if they would cause a retain cycle, that is, by using [weak self].

As a matter of fact, [weak self] is not the only means Swift provides to break retain cycles. You could also use [unowned self], which is similar to the former in that it will not retain self, but will not convert self into an optional inside of the closure body (and will not nil it when the object it refers to is freed). So, you can access self inside the closure without concerns about guards or unwrapping it, but, if it ever happens that the object has been freed, your program will crash. So you had better use it when you have alternative means to ensure the object is still there when the closure is executed.

Another approach to not creating retain cycles is passing self as an argument into the closure, as shown in the following example. Notice, though, that in this case, the self in the closure body ceases to be a keyword, becoming just a parameter name.

 func addOperationAlsoOK(op: (@escaping ()->Void)) {
self.operations.append { (self) in
DispatchQueue.global().asyncAfter(deadline: .now() + self.delay) {
op()
}
}
}

For the code above to compile, though, you will have to modify our definition of self.operations so stored closures accept an argument. This is left as an exercise for the reader.