首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >避免在闭包中捕获值副本的最佳方法

避免在闭包中捕获值副本的最佳方法
EN

Stack Overflow用户
提问于 2015-05-05 17:27:41
回答 3查看 1.1K关注 0票数 4
代码语言:javascript
运行
复制
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++
    }
}

现在的输出

代码语言:javascript
运行
复制
var a = Foo()
a.foo()

代码语言:javascript
运行
复制
Current i: 1
Closure i: 0 // I want current value here.

我想知道什么是最好的方法,以避免捕获一个象牙副本。

编辑1

是的,搬去上课是我想到的第一件也是唯一一件事,但是.这一次愚弄我以为可以用结构来实现.为什么会起作用?

代码语言:javascript
运行
复制
mutating func foo() {
    delayedPrint()
    dispatch_async(dispatch_get_main_queue(), { _ in
        println("From foo: \(self.i)")
    })
    delayedPrint()
    i++
}

输出:

代码语言:javascript
运行
复制
Current i: 1
Closure i: 0
From foo: 1
Closure i: 0
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-05-05 18:41:02

我想知道什么是最好的方法,以避免捕获一个象牙副本。

这是一种误解。你不能以这种方式“捕获一个象牙”。你要捕捉的是self!这就是为什么斯威夫特强迫你说self,这样你才能理解这个事实。因此,self是一种不同的东西。这就是为什么self是一个结构还是一个类很重要的原因。类实例是可变的;struct实例不是,因此在捕获时获取一个副本,并且该副本独立保存。

但是,您可以捕获一个简单的Int (即不是ivar),当您这样做时,您就得到了预期的结果:

代码语言:javascript
运行
复制
var i = 0
struct Foo {
    func delayedPrint() {
        dispatch_async(dispatch_get_main_queue(), {
            println(i) // 1
        })
    }
    func foo() {
        delayedPrint()
        i++
    }
}

现在让我们来谈谈你提出的第二个谜题。这里有一个重写,以澄清谜题是什么:

代码语言:javascript
运行
复制
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
        }
    }
}

我会像这样测试它

代码语言:javascript
运行
复制
var a = Foo()
a.foo()
a.bar(1)

控制台显示:

代码语言:javascript
运行
复制
From foo: 1 [after half a second]
from bar: 1 [after 1 second]
from bar: 0 [after 2 seconds]

那么,如何在1秒后的第二次调用bar,同时显示更早的self.i值呢?为什么foo的行为会有所不同?

答案与以下事实有关:所有事情都发生在函数内部--包括匿名函数的定义。代码必须在某个时候运行。在此之前,匿名函数尚未定义。

  1. 首先,让我们考虑一下a.bar(1)。这将导致bar运行并定义将捕获self的匿名函数。但是在我们调用foo和增量i之后就会发生这种情况。因此,此时捕获的self有一个递增的i
  2. 接下来,让我们考虑一下当foo调用bar时会发生什么。它在增量i之前就这样做了。因此,现在bar运行并定义了匿名函数,并捕获了i仍然设置为0的self。这个结果在两秒钟后到达控制台是不相关的;重要的是捕获发生的时间。
  3. 最后,我们讨论了foo中的匿名函数这个令人惊讶的例子。显然,i++foo中的存在决定了一切的不同。为什么?当foo运行时,它定义了一个捕捉self的匿名函数。但是,这个self也是在foo本身中捕获的,目的是为了表示i++ --实际上是self.i++。因此,这个匿名函数也可以看到i++i++上执行的更改,因为它们查看的是同一个self。 换句话说,我建议您已经碰到了匿名函数中定义的匿名函数的神秘边缘情况,该函数本身会变异self。(我不知道我是否认为这是一个bug;我将把它提交给dev论坛,看看他们怎么想。)
票数 2
EN

Stack Overflow用户

发布于 2015-05-05 17:59:49

我认为这里必须使用类而不是结构,因为结构是通过复制传递的,而类是通过引用传递的。

票数 4
EN

Stack Overflow用户

发布于 2015-05-05 18:22:16

为了补充@nikolayn的完美答案,这里有一个可以在控制台中运行的示例,它演示了如何使用类而不是结构(并且也没有数据竞争)来实现这一点:(变量是显式定义的,这样您就可以很容易地调试)

代码语言:javascript
运行
复制
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()
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30059508

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档