Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Swift closure retain captured variables?

Tags:

swift

I discovered Swift closures are not retaining the captured variables unlike my expectation.

class AAA {
}
var a1  =   AAA() as AAA?                  // expects RC == 1
var a2  =   { ()->AAA? in return a1 }      // expects RC == 2, retained by `Optional<AAA>`
a1      =   nil                            // expects RC == 1
a2()                                       // prints nil, ????

I am very confused with this because I have been believed the captured variables will be retained by default. But, if I capture it explicitly using capturing list, it is being retains.

class AAA {
}
var a1  =   AAA() as AAA?
var a2  =   { [a1]()->AAA? in return a1 }
a1      =   nil
a2() // prints {AAA}, alive as expected.

I re-read the Swift manual, but I couldn't find related description. Capturing list is used to set unowned explicitly, and I am still confused. What is correct behavior and why does this happen?

like image 208
eonil Avatar asked Oct 26 '14 15:10

eonil


People also ask

Does closure capture values in Swift?

A closure can capture constants and variables from the surrounding context in which it's defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.

Can we capture a lazy variable inside a closure or not?

Important to note: You can use self inside the closure of a lazy property. It will not cause any retain cycles. The reason is that the immediately applied closure {}() is considered @noescape .

How would you avoid retain cycles when using closures blocks in Swift?

Avoiding Retain Cycles In Closures In order for a closure to execute later, it needs to retain any variables that it needs for it to run. Similarly to classes, a closure captures references as strong by default. A retain cycle with a closure would look something like this: class SomeObject { var aClosure = { self.

Can a closure be assigned to a variable Swift?

How A Closure Works In Swift. Closures are self-contained blocks of functionality that can be passed around and used in your code. Said differently, a closure is a block of code that you can assign to a variable. You can then pass it around in your code, for instance to another function.


3 Answers

Yes that's documented in Capturing Values:

Swift determines what should be captured by reference and what should be copied by value. You don’t need to annotate amount or runningTotal to say that they can be used within the nested incrementor function. Swift also handles all memory management involved in disposing of runningTotal when it is no longer needed by the incrementor function.

The rule is: if you reference a captured variable without modifying it, it is captured by value. If instead you modify it, it is captured by reference. Of course unless you explicitly override that by defining a capturing list.

Addendum The above statements appears to be incorrect. Captures are made by reference regardless of whether they are modified or not inside the closure. Read @newacct comment.

like image 111
Antonio Avatar answered Oct 22 '22 05:10

Antonio


Your example doesn't make sense @newacct. The variable x is actually modified outside the block. Swift is smart enough to find out whether you modify a variable inside or outside the closure.

As the document says:

As an optimization, Swift may instead capture and store a copy of a value if that value is not mutated by or outside a closure.

As to the question post by @Eonil, in the first snippet, you have a strong reference to Optional<AAA> in closure. However when you set a1 to nil, what you actually do is removing the value of type AAA which is wrapped inside the optional.

When you call closure a2, you return the same optional with no value inside, which is exactly a nil means. So, holding a strong reference means your closure shares the same optional value as the a1. It doesn't means this optional can't be set to nil.

In your second code snippet, you captured a1 within the capture list. This means you copy the a1 and the value a1 inside the closure has nothing to do with the value a1 outside the closure. So even if you set a1 to nil, it doesn't affect what you get from the closure.

My English is not good and I hope I expressed my opinion clearly. Or you can read this article, I believe it will help you a lot.

like image 37
sevenkplus Avatar answered Oct 22 '22 04:10

sevenkplus


I posted same question on Apple Developer Forum, and there was a discussion. Though people are not talking about reference counting a lot, but I got some ideas. Here's my conclusion:

  • A value is always copied when it is bound to a new (explicit) name. Regardless of var or let.
  • Copied in reference types means RC+1. Because it's copying of strong pointer like C++ shared_ptr<T>.
  • Closing (closures capturing) does not change anything because there's no new name. It's same as you use them in same stack. This is what by-reference means. Nothing related to RC, and does not change RC because it's something like references in C++.
  • Capturing list is an explicit (let) name. So it causes copying and it makes RC+1.
  • When closure is returned from a function, it may be bound to a name. If it did, RC+1 because of new binding to a name.
  • RC-1 when a value (so a strong pointer) unbound from its name.
  • Implicit reference to self is the only exception which makes implicit name binding.

And there're many optimisations without breaking these rules, anyway those are just implementation details.

like image 39
eonil Avatar answered Oct 22 '22 04:10

eonil