struct Foo {
var i = 0 { didSet { println("Current i: \(i)") } }
func delayedPrint() {
dispatch_async(dispatch_get_main_queue(), { _ in
println("Closure i: \(self.i)")
})
}
mutating func foo() {
delayedPrint()
i++
}
}
现在输出
var a = Foo()
a.foo()
是
Current i: 1
Closure i: 0 // I want current value here.
我想知道什么是避免捕获ivar副本的最佳方式。
编辑1
是的,上课是第一次也是我唯一想到的,但是......这个骗过我,认为它可以用结构以某种方式完成......为什么会这样?
mutating func foo() {
delayedPrint()
dispatch_async(dispatch_get_main_queue(), { _ in
println("From foo: \(self.i)")
})
delayedPrint()
i++
}
输出:
Current i: 1
Closure i: 0
From foo: 1
Closure i: 0
答案 0 :(得分:4)
我认为你必须在这里使用类而不是结构,因为结构通过引用传递副本和类
答案 1 :(得分:2)
我想知道什么是避免捕获ivar副本的最佳方式。
这是一种误解。你不能抓住一个伊娃"通过这种方式。你捕获的是self
!这正是Swift迫使你说 self
的原因,以便你理解这个事实。因此,self
是是什么样的事情。这就是为什么self
是结构还是类的重要性。类实例是可变的;结构实例不是,因此在捕获时会复制该副本,并且该副本会独立存在。
但是,你可以捕获一个简单的Int(即不一个ivar),当你这样做时,你会得到你期望的结果:
var i = 0
struct Foo {
func delayedPrint() {
dispatch_async(dispatch_get_main_queue(), {
println(i) // 1
})
}
func foo() {
delayedPrint()
i++
}
}
现在让我们谈谈你摆出的第二个谜题。这是一个重写,以澄清谜题是什么:
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
struct Foo {
var i = 0
mutating func foo() {
delay(0.5) {
println("From foo: \(self.i)") // 1
}
bar(2)
i++
}
func bar(d:Double) {
delay(d) {
println("from bar: \(self.i)") // 0
}
}
}
我会像这样测试它:
var a = Foo()
a.foo()
a.bar(1)
控制台显示:
From foo: 1 [after half a second]
from bar: 1 [after 1 second]
from bar: 0 [after 2 seconds]
那么bar
如何在第二次以后第二次调用,但显示之前的值self.i
?为什么foo
表现不同?
答案与事务内部发生的一切事实有关 - 包括匿名函数的定义。代码必须在某些时候运行。在此之前,未定义匿名函数。
首先考虑a.bar(1)
。这会导致bar运行并定义将捕获self
的匿名函数。但这发生在之后我们调用foo
并递增i
。因此,此时捕获的self
会增加i
。
接下来让我们考虑foo
调用bar
时会发生什么。它在递增i
之前执行此操作。所以现在bar
运行并且定义了匿名函数,并且self
的{{1}}仍然设置为0.这个结果在两秒钟后到达控制台的事实无关紧要;重要的是捕获发生的时间。
最后,我们来到i
内部匿名函数的令人惊讶的情况。显然,foo
内i++
的存在会产生重大影响。为什么?好吧,当foo
运行时,它定义了一个捕获foo
的匿名函数。但此 self
已在self
内捕获<{1}},目的是说foo
- 这真的是{{1} }。因此,这个匿名函数也可以看到i++
执行self.i++
的更改,因为它们正在查看相同的self
。
换句话说,我建议您点击一个本身变异i++
的函数中定义的匿名函数的神秘边缘案例。 (我不知道我是否认为这是一个错误;我将把它提交给开发论坛,看看他们的想法。)
答案 2 :(得分:0)
为了补充@nikolayn的完美答案,这里有一个可以在控制台中运行的示例,它演示了如何使用类而不是结构来完成此操作(并且还没有数据竞争): (变量是显式定义的,以便您可以轻松调试)
import Foundation
import Cocoa
let queue = dispatch_queue_create("sync_queue", nil)!
class Foo {
var i = 0 { didSet { println("Current i: \(i)") } }
let sem = dispatch_semaphore_create(0);
func delayedPrint() {
let i_copy = i
let t = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(t, queue) { _ in
let _i = self.i
let _i_copy = i_copy
println("Closure i: \(_i)")
println("Closure i_copy: \(_i_copy)")
dispatch_semaphore_signal(self.sem)
}
}
func foo() {
delayedPrint()
dispatch_async(queue) {
self.i++
}
}
func wait() {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
}
}
var a = Foo()
a.foo()
a.wait()