使用 Swift 的 Grand Central Dispatch 出现问题

Trouble with Grand Central Dispatch using Swift

我有以下函数,它的行为与我预期的不一样。

func dispatchTrouble(startValue:Float, endValue:Float, duration:Float)
{
    //100 increment steps per second
    let incrementSteps = duration * 100

    for(var step = 0.0 as Float; step < incrementSteps; step++)
    {
        var delayInSeconds = step * (duration / incrementSteps)
        let answer = Float(NSEC_PER_SEC) * delayInSeconds
        let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(answer))
        println(step)

        //Using GCD to perform this on the main thread.
        dispatch_after(popTime, dispatch_get_main_queue()){
            println(step)
            let fraction = step / incrementSteps
            let incrementedValue = startValue + (endValue - startValue) * fraction
            println(incrementedValue)
        }
    }
}

我希望 println(incrementedValue) 语句显示一个从 startValue 递增到 endValue 的值,并在 duration 中传递的秒数内完成。

然而,我得到的行为是 dispatch_after 闭包中的代码只打印最终值,它从不打印增量。

延迟按预期发生,但所有值的计算都好像 for 循环已经完成。第一个 println(step) 确实显示步长递增,但第二个只显示最终值。

我显然对这应该如何工作有误解。我原以为闭包中的代码会保留调用 dispatch_after 方法时存在的值,但它的行为就像它使用实际执行时的任何值一样。

如何在 for 循环的每次迭代中捕获值并使用它在闭包中执行代码?

您发送给 GDC 的所有闭包都指向同一个 step 变量。这意味着每次执行其中一个时,它都具有循环结束时的值。

尝试将您的代码更改为:

func dispatchTrouble(startValue:Float, endValue:Float, duration:Float)
{
    //100 increment steps per second
    let incrementSteps = duration * 100

    for(var step = 0.0 as Float; step < incrementSteps; step++)
    {
        var delayInSeconds = step * (duration / incrementSteps)
        let answer = Float(NSEC_PER_SEC) * delayInSeconds
        let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(answer))
        println(step)

        let stepCopy = step

        //Using GCD to perform this on the main thread.
        dispatch_after(popTime, dispatch_get_main_queue()){
            println(stepCopy)
            let fraction = stepCopy / incrementSteps
            let incrementedValue = startValue + (endValue - startValue) * fraction
            println(incrementedValue)
        }
    }
}

它会像那样工作。如 the swift reference.

中所述,闭包在 step 上进行保留

不同于捕获的Objective-C块,Swift闭包捕获变量。这意味着 Objective-C 块会捕获 "step" 变量的 100 个不同值,而 Swift 闭包会捕获变量本身并在调用闭包时打印其值。

解决此问题的最佳方法是添加捕获列表。

dispatch_after(popTime, dispatch_get_main_queue()){
    [let stepcopy = step] () -> Void in
    println(stepcopy)
    let fraction = stepcopy / incrementSteps
    let incrementedValue = startValue + (endValue - startValue) * fraction
    println(incrementedValue)
}

所以闭包以 { 开头,后跟可选的 [ 方括号中的捕获列表],后跟可选的 (arguments) -> result in,然后是代码。

顺便说一句,通过使用 Float 而不是 Double,您可以无缘无故地将精度降低到大约 7 位而不是 15 位。