一尘不染

异步回调中的Inout参数无法按预期工作

swift

我正在尝试插入带有inout参数的函数,以将从异步回调接收的数据追加到外部数组。但是,它不起作用。我尽一切所能找出原因-没运气。

正如@AirspeedVelocity所建议的那样,我将代码重写如下,以删除不必要的依赖项。我还使用an Int作为inout参数来保持简单。
输出始终为:
c before: 0
c after: 1

我无法弄清楚这里出了什么问题。

func getUsers() {
    let u = ["bane", "LiweiZ", "rdtsc", "ssivark", "sparkzilla", "Wogef"]
    var a = UserData()
    a.userIds = u
    a.dataProcessor()
}

struct UserData {
    var userIds = [String]()
    var counter = 0
    mutating func dataProcessor() -> () {
        println("counter: \(counter)")
        for uId in userIds {
            getOneUserApiData(uriBase + "user/" + uId + ".json", &counter)
        }
    }
}

func getOneUserApiData(path: String, inout c: Int) {
    var req = NSURLRequest(URL: NSURL(string: path)!)
    var config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
    var session = NSURLSession(configuration: config)
    var task = session.dataTaskWithRequest(req) {
        (data: NSData!, res: NSURLResponse!, err: NSError!) in
        println("c before: \(c)")
        c++
        println("c after: \(c)")
        println("thread on: \(NSThread.currentThread())")
    }

    task.resume()
}

谢谢。


阅读 216

收藏
2020-07-07

共1个答案

一尘不染

可悲的是,修改inout在异步回调参数是没有意义的。

官方文件

参数可以提供默认值以简化函数调用,并且可以作为输入-输出参数传递,参数 在函数完成执行后会 修改传递的变量。

输入输出参数具有一个值,该值传递给函数,由函数修改,然后从函数 传递回 以替换原始值。

语义上,输入输出参数不是“按引用调用,而是“按复制副本恢复”

就您而言,counter仅在getOneUserApiData()返回时才回写,而不在dataTaskWithRequest()回调中。

这是您的代码中发生的事情

  1. getOneUserApiData()调用时,将值counter 0复制到c1
  2. 闭合捕获c1
  3. 呼叫 dataTaskWithRequest()
  4. getOneUserApiData返回,并且-未经修改c-1的值被写回到counter
  5. 重复1-4步骤为c2,c3,c4 …
  6. …从互联网上获取…
  7. 回调被调用,并且c1递增。
  8. 回调被调用,并且c2递增。
  9. 回调被调用,并且c3递增。
  10. 回调被调用,并且c4递增。

结果counter是未修改的:(


详细说明

通常,in-out参数是通过引用传递的,但这只是编译器优化的结果。当闭包捕获inout参数时,“传递引用”是 不安全的
,因为编译器无法保证原始值的生存期。例如,考虑以下代码:

func foo() -> () -> Void {
    var i = 0
    return bar(&i)
}

func bar(inout x:Int) -> () -> Void {
    return {
        x++
        return
    }
}

let closure = foo()
closure()

在此代码中,返回var ifoo()将其释放。如果x是对的引用ix++则会导致访问冲突。为了防止出现这种竞争情况,Swift在此处采用了“按副本还原呼叫”策略。

2020-07-07