J. Doe
J. Doe

Reputation: 13043

What difference does it make to include (or not include) a capture list in nonescaping closures?

This is my code:

class Person { 
    let age: Int

    init(age: Int) { 
        self.age = age
    }

    func callAgePrinter() { 
        // THIS LINE
        AgePrinter.printAgeOf(person: { return self } )
    }
}

class AgePrinter {
    static func printAgeOf(person: () -> (Person)) { 
        print(person().age)
    }
}

Person(age: 1).callAgePrinter()

Notice the comment: // THIS LINE

I am able to transform this line:

AgePrinter.printAgeOf(person: { return self } )

To:

AgePrinter.printAgeOf(person: { [unowned self] in return self } )

The result of the code is the same.

I am wondering: for nonescaping closures (which apply for Swift 4.2 only at function declaration level), what is the use a capture list? It is guaranteed for nonescaping closures that after the method is ended in which the closure is (or may not) be called, the closure is gone and not stored.

Ofcourse, a retain cycle can still occur when storting the output of the closure in my example. However, a capture cycle does not prevent this.

What difference does it make to include (or not include) a capture list in nonescaping closures?

Upvotes: 2

Views: 62

Answers (2)

Rob Napier
Rob Napier

Reputation: 299355

It is common to use capture lists for self, but that is not their only purpose. A capture list captures arbitrary values. And by "capture," I mean "makes its own shadow copy rather than closing over the value in its scope." Consider the following code:

var a = 10

let f: () -> Void = { print("f: \(a)") }

let g: () -> Void = { [a] in print("g: \(a)") }

a = 20
f()   // f: 20; using "a" from the local scope surrounding f()
g()   // g: 10; using "a" that was copied into g()

As a slightly more complex example, consider the following (incorrect) code where we accumulate some actions to perform, using some value that needs to increment from action to action:

var actions: [() -> Void] = []

var opId = 0
if someCase {
    opId += 1
    actions.append({ doSomethingWith(opId) })
}
if anotherCase {
    opId +=1
    actions.append({ doSomethingElseWith(opId) })
}
//...
actions.forEach { $0() }

This code is incorrect; all of the closures will have the same opId. However, if we add a capture list, we can resolve the problem:

actions.append({ [opId] in doSomethingWith(opId) })

Upvotes: 3

Vyacheslav
Vyacheslav

Reputation: 27221

Capture copies the information inside the variable inside a new one. I mean [unowned self] creates another self variable which is not actually a self.

Upvotes: 2

Related Questions