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?
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.
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 .
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.
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.
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.
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.
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:
var
or let
.shared_ptr<T>
.let
) name. So it causes copying and it makes RC+1.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With