我尝试插入带有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()
}
感谢。
答案 0 :(得分:16)
不幸的是,修改inout
在异步回调参数是无意义的。
参数可以提供默认值来简化函数调用,并且可以作为输入输出参数传递,这些参数在函数完成执行后修改传递的变量 。
...
输入输出参数具有传递给函数的值,由函数修改,并且传回退出函数以替换原始值。
从语义上讲,输入输出参数不是"call-by-reference",而是"call-by-copy-restore"。
在您的情况下,counter
仅在getOneUserApiData()
返回时被写入支持,而不是dataTaskWithRequest()
回调。
以下是您的代码中发生的事情
getOneUserApiData()
来电时,counter
0
的值已复制到c
1 c
1 dataTaskWithRequest()
getOneUserApiData
返回,并且 - 未修改 - c
1 的值被写入后备counter
c
2 ,c
3 ,c
4 的程序... c
1 递增。c
2 递增。c
3 递增。c
4 递增。因此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 i
返回时释放foo()
。如果x
是i
的引用,则x++
会导致访问冲突。为了防止这种竞争条件,Swift采用了“逐个复制恢复”策略。
答案 1 :(得分:6)
基本上看起来你试图在闭包中捕获输入变量的“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
。
答案 2 :(得分:0)
我有一个类似的目标并遇到了同样的问题,其中闭包内的结果没有分配给我的全局输入变量。 @rintaro很好地解释了为什么在之前的答案中出现这种情况。
我将在这里包含一个关于我如何解决这个问题的概括示例。在我的例子中,我想要在闭包中分配几个全局数组,然后每次都做一些事情(不重复一堆代码)。
$(".frequency_new_selector").on("change", function() {
if (this.value === '1') {
event.preventDefault();
$('.cd-popup').addClass('is-visible');
}
});
答案 3 :(得分:0)
@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()
}