Swift: Does closure have references to constants o

2020-02-28 07:54发布

问题:

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?

回答1:

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.)