【问题标题】:Inout parameter in async callback does not work as expected异步回调中的 inout 参数无法按预期工作
【发布时间】:2015-01-31 00:49:11
【问题描述】:

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

根据@AirspeedVelocity 的建议,我重写了如下代码以删除不必要的依赖项。我还使用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()
}

谢谢。

【问题讨论】:

  • 您需要将代码简化为更简单的情况(最好不使用外部库)并将其作为问题的一部分发布。如果他们必须通读几页代码才能得到你想要做的事情,你可能不会从任何人那里得到好的答案。
  • @AirspeedVelocity 谢谢。我会尽快修改。
  • 我刚刚发布了一个问题,以了解如何进行修改。这是我知道 inout 在这种情况下不起作用之后的另一个问题。 stackoverflow.com/questions/28254254/…

标签: swift


【解决方案1】:

很遗憾,在async-callback中修改inout参数是没有意义的。

来自the official document

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

...

in-out 参数有一个值,传入函数,被函数修改,传回函数外替换原来的值。

从语义上来说,in-out 参数不是"call-by-reference",而是"call-by-copy-restore"

在您的情况下,counter 仅在 getOneUserApiData() 返回时被写回,而不是在 dataTaskWithRequest() 回调中。

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

  1. getOneUserApiData()调用时,counter的值0复制到c1
  2. 闭包捕获c1
  3. 致电dataTaskWithRequest()
  4. getOneUserApiData 返回,并且 - 未修改 - c1 的值回写到 counter
  5. c2c3c4 重复 1-4 过程 ...
  6. ...从 Internet 获取...
  7. 回调被调用,c1 递增。
  8. 回调被调用,c2 递增。
  9. 回调被调用,c3 递增。
  10. 回调被调用,c4 递增。
  11. ...

因此counter 未被修改:(


详细说明

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

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 是对i 的引用,x++ 会导致访问冲突。为了防止这种竞争情况,Swift 在这里采用了“call-by-copy-restore”策略。

【讨论】:

  • 谢谢你们这么好的回答。我在另一个答案下写了我选择你的理由作为评论。
  • 很好的答案,但令人失望。有没有其他方法可以构建 OP 的示例来实现目标?
【解决方案2】:

从本质上讲,您似乎正试图在闭包中捕获输入变量的“inout-ness”,而您不能这样做 - 考虑以下更简单的情况:

// f takes an inout variable and returns a closure
func f(inout i: Int) -> ()->Int {
    // this is the closure, which captures the inout var
    return {
        // in the closure, increment that var
        return ++i
    }

}

var x = 0
let c = f(&x)

c() // these increment i
c()
x   // but it's not the same i

在某些时候,传入的变量不再是x,而是变成了一个副本。这可能发生在捕获点。

编辑:@rintaro 的回答很到位——inout 实际上在语义上并不是通过引用传递

如果你仔细想想,这是有道理的。如果你这样做会怎样:

// declare the variable for the closure
var c: ()->Int = { 99 }

if 2+2==4 {
    // declare x inside this block
    var x = 0
    c = f(&x)
}

// now call c() - but x is out of scope, would this crash?
c()

当闭包捕获变量时,需要在内存中创建它们,即使在声明它们的范围结束后它们也可以保持活动状态。但是在上面f的情况下,它不能这样做——这样声明x已经来不及了,x已经存在。所以我猜它会作为闭包创建的一部分被复制。这就是为什么增加闭包捕获的版本实际上不会增加​​x

【讨论】:

  • 我赞成这两个答案,因为它们都很棒。但是,我只能选择一个作为接受的答案。我会选择@rintaro 的那个,因为它更接近上下文(示例代码)并且附带了详细的解释。顺便说一句,谢谢你的博客。我读了很多遍,从中学到了很多。
  • 没有难过的感觉@rintaro 是一个更好的答案 :-)
  • 这是实际完成任务的答案。这是一个很好的解决方法。谢谢!
【解决方案3】:

我有一个类似的目标并遇到了同样的问题,即闭包内的结果没有分配给我的全局 inout 变量。 @rintaro 在之前的回答中很好地解释了为什么会出现这种情况。

我将在这里包含一个通用示例,说明我是如何解决这个问题的。在我的例子中,我有几个全局数组,我想在一个闭包中分配它们,然后每次都做一些事情(不重复一堆代码)。

// global arrays that we want to assign to asynchronously
var array1 = [String]()
var array2 = [String]()
var array3 = [String]()

// kick everything off
loadAsyncContent()

func loadAsyncContent() {

    // function to handle the query result strings
    // note that outputArray is an inout parameter that will be a reference to one of our global arrays
    func resultsCallbackHandler(results: [String], inout outputArray: [String]) {

        // assign the results to the specified array
        outputArray = results

        // trigger some action every time a query returns it's strings
        reloadMyView() 
    }

    // kick off each query by telling it which database table to query and
    // we're also giving each call a function to run along with a reference to which array the results should be assigned to
    queryTable("Table1") {(results: [String]) -> Void in resultsCallbackHandler(results, outputArray: &self.array1)}
    queryTable("Table2") {(results: [String]) -> Void in resultsCallbackHandler(results, outputArray: &self.array2)}
    queryTable("Table3") {(results: [String]) -> Void in resultsCallbackHandler(results, outputArray: &self.array3)}
}

func queryTable(tableName: String, callback: (foundStrings: [String]) -> Void) {

    let query = Query(tableName: tableName)
    query.findStringsInBackground({ (results: [String]) -> Void in

        callback(results: results)
    })
}

// this will get called each time one of the global arrays have been updated with new results
func reloadMyView() {

    // do something with array1, array2, array3
}

【讨论】:

    【解决方案4】:

    @rintaro 完美地解释了为什么它不起作用,但如果你真的想这样做,使用 UnsafeMutablePointer 就可以了:

    func getOneUserApiData(path: String, c: UnsafeMutablePointer<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.memory)")
            c.memory++
            println("c after: \(c.memory)")
            println("thread on: \(NSThread.currentThread())")
        }
    
        task.resume()
    }
    

    【讨论】:

      猜你喜欢
      • 2017-04-17
      • 1970-01-01
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 2019-12-29
      • 2021-04-02
      • 1970-01-01
      • 2019-08-29
      相关资源
      最近更新 更多