Roland
Roland

Reputation: 489

Optimizing capture lists

Is there such a thing? Is there any difference between the two below? Is one more "correct" than the other?

All objects are properties of self (let's say a view controller) and have the same lifetime as self. We can introduce an object with a shorter lifetime than self, which would be weak, but the same question applies.

objectOne.doSomething { [unowned self] in
    self.objectTwo.finish()
    self.tableView.reloadData()
    // self.someDelegate?.didFinishSomething()
}

vs

objectOne.doSomething { 
    [unowned objectTwo = self.objectTwo,
    unowned tableView = self.tableView
    // weak someDelegate = self.delegate
    ] in
    objectTwo.finish()
    tableView.reloadData()
    // someDelegate?.didFinishSomething()
}

Apple has this example in their docs:

lazy var someClosure: () -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // closure body goes here

    delegate?.doSomething()
}

In this case, delegate can have a shorter lifetime than self, but why not use it like this?

lazy var someClosure: () -> String = { 
    [unowned self] in
    // closure body goes here

    self.delegate?.doSomething()
}

Upvotes: 4

Views: 91

Answers (2)

Roland
Roland

Reputation: 489

One of the problems that [unowned objectOne = self.objectOne] might cause is with lazy var UIViews and racing conditions: if you aren't careful and the lazy init gets called from different threads, two instances might end up being created. Furthermore, if your superview.addSubview(objectOne) call is in the lazy init, both instances will be added to the superview, and objectOne will point to one of the two instances.

Upvotes: 0

Daniel Hall
Daniel Hall

Reputation: 13679

Yes, there is an important difference. In the case of the Apple docs, the code alternative you presented:

lazy var someClosure: () -> String = { 
    [unowned self] in
    // closure body goes here

    self.delegate?.doSomething()
}

will look up the current delegate on self when the closure runs.

In the Apple example version:

lazy var someClosure: () -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // closure body goes here

    delegate?.doSomething()
}

the weak delegate var in the capture list is copying the delegate pointer on self that exists at the time of closure declaration, not execution. So if the value of self.delegate changes after the closure is declared and is different at the time the closure runs, the Apple version of the closure will have a nil delegate (assumedly since the reference to the old delegate was weak) and do nothing.

So as a general rule, copying values or references in capture lists ([someIdentifier = someProperty]) is how to use the values or references as they exist at the moment the closure is defined. Whereas declaring weak or unowned self in the capture list ([weak self]) and then accessing properties on that weak reference ({ self?.someProperty }) will get the values of the properties as they exist when the closure is executed.

Upvotes: 1

Related Questions