Consider the following code:
func foo(inout success: Bool) -> (()->()) {
return { _ in
success = true
print (success)
}
}
var success = false
let closure = foo(&success)
closure() //prints "true"
print(success) //prints "false"
The closure appears to be creating a copy of success and does not change the original. Why is this taking place? I had assumed that the closure would point to the original because we are passing an inout
variable.
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.
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.
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.
The Basics. In Swift, structs, enums and tuples are all value types, while classes and closures are reference types. In a nutshell, a value type contains data and a reference type contains the location in memory where data lives.
It makes sense that this wouldn't update your success
variable because your inout
parameter is a parameter of foo
, not of the closure itself. You get the desired behavior if you make the inout
parameter a parameter of the closure:
var success = false
let closure = { (inout flag: Bool) -> () in
flag = true
print(flag)
}
closure(&success) //prints "true"
print(success) //prints "true"
This pattern also works with the function, too, as long as you keep the inout
parameter a parameter of the closure:
func foo() -> ((inout Bool)->()) {
return { flag in
flag = true
print (flag)
}
}
var success = false
let closure = foo()
closure(&success) //prints "true"
print(success) //prints "true"
You also get the desired behavior if you use a reference type:
class BooleanClass: CustomStringConvertible {
var value: Bool
init(value: Bool) {
self.value = value
}
var description: String { return "\(value)" }
}
func foo(flag: BooleanClass) -> (()->()) {
return {
flag.value = true
print (flag)
}
}
let success = BooleanClass(value: false)
let closure = foo(success)
closure() //prints "true"
print(success) //prints "true"
This seems to be covered by Swift Evolution proposal 0035, and is considered a bug.
The document there refers to the inout
parameter to the function as "a shadow copy that is written back to the argument when the callee returns". This seems to mean that there is, in essence, a temporary variable named success
in the executing context of foo()
. The value of that temp is then put into the outer success
only when foo()
returns.
Since in your foo()
, the closure has not run when foo()
returns, the value of the "shadow copy" has not changed. The outer success
keeps its value.
The important part is that the closure has captured the shadow copy, not the outer success
as you expect. So when the closure finally runs, that variable's value does change, but it no longer has any connection to the original outer success
.
The proposal uses this snippet to demonstrate:
func captureAndEscape(inout x: Int) -> () -> Void { let closure = { x += 1 } closure() return closure } var x = 22 let closure = captureAndEscape(&x) print(x) // => 23 closure() print("still \(x)") // => still 23
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