I know there are several related question and moreover I can find many posts in the Internet.
However, I can't understand the fact that closures can hold references. In case of a reference type, it is totally usual and very reasonable, but how about a value type, including struct
and enum
?
See this code.
let counter: () -> Int
var count = 0
do {
counter = {
count += 1
return count
}
}
count += 1 // 1
counter() // 2
counter() // 3
We can access the value type count
through two ways. One is by using count
directly and the another is through the closure counter
.
However, if we write
let a = 0
let b = a
, in the memory b
has of course a different area with a
because they are value type. And this behavior is a distinct feature of value type which is different with reference type.
And then backing to the closure topic, closure has the reference to value type's variable or constant.
So, can I say the value type's feature that we can't have any references to value type is changed in case of closure's capturing values? To me, capturing references to value type is very surprising and at the same time the experience I showed above indicates that.
Could you explain this thing?
The Basics. In Swift, structs, enums and tuples are all value types, while classes and closures are reference types.
The closure captures the variable. This is a variable on module level, therefore its scope always exists. When you reassign its value, it will affect the value read inside the closure.
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.
In Swift, closures capture the variables they reference: variables declared outside of the closure but that you use inside the closure are retained by the closure by default, to ensure they are still alive when the closure is executed.
I think the confusion is by thinking too hard about value types vs reference types. This has very little to do with that. Let's make number be reference types:
class RefInt: CustomStringConvertible {
let value: Int
init(value: Int) { self.value = value }
var description: String { return "\(value)" }
}
let counter: () -> RefInt
var count = RefInt(value: 0)
do {
counter = {
count = RefInt(value: count.value + 1)
return count
}
}
count = RefInt(value: count.value + 1) // 1
counter() // 2
counter() // 3
Does this feel different in any way? I hope not. It's the same thing, just in references. This isn't a value/reference thing.
The point is that, as you note, the closure captures the variable. Not the value of the variable, or the value of the reference the variable points to, but the variable itself). So changes to the variable inside the closure are seen in all other places that have captured that variable (including the caller). This is discussed a bit more fully in Capturing Values.
A bit deeper if you're interested (now I'm getting into a bit of technicalities that may be beyond what you care about right now):
Closures actually have a reference to the variable, and changes they make immediately occur, including calling didSet
, etc. This is not the same as inout
parameters, which assign the value to their original context only when they return. You can see that this way:
let counter: () -> Int
var count = 0 {
didSet { print("set count") }
}
do {
counter = {
count += 1
print("incremented count")
return count
}
}
func increaseCount(count: inout Int) {
count += 1
print("increased Count")
}
print("1")
count += 1 // 1
print("2")
counter() // 2
print("3")
counter() // 3
increaseCount(count: &count)
This prints:
1
set count
2
set count
incremented count
3
set count
incremented count
increased Count
set count
Note how "set count" is always before "incremented count" but is after "increased count." This drives home that closures really are referring to the same variable (not value or reference; variable) that they captured, and why we call it "capturing" for closures, as opposed to "passing" to functions. (You can also "pass" to closures of course, in which case they behave exactly like functions on those parameters.)
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