Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Does closure have references to constants or variables?

Tags:

closures

swift

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?

like image 731
Kazuya Tomita Avatar asked Jun 20 '17 00:06

Kazuya Tomita


People also ask

Are closures value or reference types Swift?

The Basics. In Swift, structs, enums and tuples are all value types, while classes and closures are reference types.

Does closure capture values in Swift?

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.

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.

How do closures capture references to variables by default?

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.


1 Answers

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

like image 111
Rob Napier Avatar answered Nov 09 '22 23:11

Rob Napier