在转义闭包中究竟如何捕获结构?
How exactly are structs captured in escaping closures?
在我看来,转义闭包会通过复制捕获结构是合乎逻辑的。但如果是这样的话,下面的代码就没有意义,不应该编译:
struct Wtf {
var x = 1
}
func foo(){
var wtf = Wtf()
DispatchQueue.global().async {
wtf.x = 5
}
Thread.sleep(forTimeInterval: 2)
print("x = \(wtf.x)")
}
然而它编译成功,甚至在调用 foo 时打印 5。这怎么可能?
虽然复制结构可能有意义,但正如您的代码所示,事实并非如此。这是一个强大的工具。例如:
func makeCounter() -> () -> Int {
var n = 0
return {
n += 1 // This `n` is the same `n` from the outer scope
return n
}
// At this point, the scope is gone, but the `n` lives on in the closure.
}
let counter1 = makeCounter()
let counter2 = makeCounter()
print("Counter1: ", counter1(), counter1()) // Counter1: 1 2
print("Counter2: ", counter2(), counter2()) // Counter2: 1 2
print("Counter1: ", counter1(), counter1()) // Counter1: 3 4
如果 n
被复制到闭包中,这将无法工作。重点是闭包捕获并可以在自身外部修改状态。这就是将闭包(它“关闭”它创建的范围)和匿名函数(不关闭)的区别。
(术语“关闭”的历史有点晦涩。它指的是 lambda 表达式的自由变量已被“关闭”的想法,但 IMO“绑定”将是一个更明显的术语,这也是我们在其他地方描述它的方式。但是“闭包”这个词已经使用了几十年,所以我们来了。)
请注意,可以获取复制语义。你只需要请求它:
func foo(){
var wtf = Wtf()
DispatchQueue.global().async { [wtf] in // Make a local `let` copy
var wtf = wtf // To modify it, we need to make a `var` copy
wtf.x = 5
}
Thread.sleep(forTimeInterval: 2)
// Prints 1 as you expected
print("x = \(wtf.x)")
}
在 C++ 中,lambda 必须明确说明如何通过绑定或复制来捕获值。但在 Swift 中,他们选择将绑定设置为默认值。
至于为什么在 wtf
被闭包捕获后允许您访问它,那只是 Swift 中缺少移动语义。 Swift 今天没有办法表达“这个变量已经传递给其他东西并且可能不再在此范围内访问”。这是该语言的一个已知限制,需要做大量工作来修复它。有关更多信息,请参阅 The Ownership Manifesto。
在我看来,转义闭包会通过复制捕获结构是合乎逻辑的。但如果是这样的话,下面的代码就没有意义,不应该编译:
struct Wtf {
var x = 1
}
func foo(){
var wtf = Wtf()
DispatchQueue.global().async {
wtf.x = 5
}
Thread.sleep(forTimeInterval: 2)
print("x = \(wtf.x)")
}
然而它编译成功,甚至在调用 foo 时打印 5。这怎么可能?
虽然复制结构可能有意义,但正如您的代码所示,事实并非如此。这是一个强大的工具。例如:
func makeCounter() -> () -> Int {
var n = 0
return {
n += 1 // This `n` is the same `n` from the outer scope
return n
}
// At this point, the scope is gone, but the `n` lives on in the closure.
}
let counter1 = makeCounter()
let counter2 = makeCounter()
print("Counter1: ", counter1(), counter1()) // Counter1: 1 2
print("Counter2: ", counter2(), counter2()) // Counter2: 1 2
print("Counter1: ", counter1(), counter1()) // Counter1: 3 4
如果 n
被复制到闭包中,这将无法工作。重点是闭包捕获并可以在自身外部修改状态。这就是将闭包(它“关闭”它创建的范围)和匿名函数(不关闭)的区别。
(术语“关闭”的历史有点晦涩。它指的是 lambda 表达式的自由变量已被“关闭”的想法,但 IMO“绑定”将是一个更明显的术语,这也是我们在其他地方描述它的方式。但是“闭包”这个词已经使用了几十年,所以我们来了。)
请注意,可以获取复制语义。你只需要请求它:
func foo(){
var wtf = Wtf()
DispatchQueue.global().async { [wtf] in // Make a local `let` copy
var wtf = wtf // To modify it, we need to make a `var` copy
wtf.x = 5
}
Thread.sleep(forTimeInterval: 2)
// Prints 1 as you expected
print("x = \(wtf.x)")
}
在 C++ 中,lambda 必须明确说明如何通过绑定或复制来捕获值。但在 Swift 中,他们选择将绑定设置为默认值。
至于为什么在 wtf
被闭包捕获后允许您访问它,那只是 Swift 中缺少移动语义。 Swift 今天没有办法表达“这个变量已经传递给其他东西并且可能不再在此范围内访问”。这是该语言的一个已知限制,需要做大量工作来修复它。有关更多信息,请参阅 The Ownership Manifesto。