I'm trying to insert functions with inout
parameter to append data received from async callback to an outside array. However, it does not work. And I tried everything I know to find out why - with no luck.
As advised by @AirspeedVelocity, I rewrote the code as follows to remove unnecessary dependencies. I also use an Int
as the inout
parameter to keep it simple.
The output is always:c before: 0
c after: 1
I'm not able to figure out what goes wrong here.
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()
}
Thanks.
An INOUT parameter is both an input and an output parameter. You can use the DEFAULT keyword to define the default value for an INOUT parameter as either an expression or NULL.
The async method can't declare any in, ref or out parameters, nor can it have a reference return value, but it can call methods that have such parameters.
Callbacks are not asynchronous by nature, but can be used for asynchronous purposes. In this code, you define a function fn , define a function higherOrderFunction that takes a function callback as an argument, and pass fn as a callback to higherOrderFunction .
Sad to say, modifying inout
parameter in async-callback is meaningless.
From the official document:
Parameters can provide default values to simplify function calls and can be passed as in-out parameters, which modify a passed variable once the function has completed its execution.
...
An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value.
Semantically, in-out parameter is not "call-by-reference", but "call-by-copy-restore".
In your case, counter
is write-backed only when getOneUserApiData()
returns, not in dataTaskWithRequest()
callback.
Here is what happened in your code
getOneUserApiData()
call, the value of counter
0
copied to c
1
c
1
dataTaskWithRequest()
getOneUserApiData
returns, and the value of - unmodified - c
1 is write-backed to counter
c
2, c
3, c
4 ...c
1 is incremented.c
2 is incremented.c
3 is incremented.c
4 is incremented.As a result counter
is unmodified :(
Detailed explaination
Normally, in-out
parameter is passed by reference, but it's just a result of compiler optimization. When closure captures inout
parameter, "pass-by-reference" is not safe, because the compiler cannot guarantee the lifetime of the original value. For example, consider the following code:
func foo() -> () -> Void {
var i = 0
return bar(&i)
}
func bar(inout x:Int) -> () -> Void {
return {
x++
return
}
}
let closure = foo()
closure()
In this code, var i
is freed when foo()
returns. If x
is a reference to i
, x++
causes access violation. To prevent such race condition, Swift adopts "call-by-copy-restore" strategy here.
Essentially it looks like you’re trying to capture the “inout-ness” of an input variable in a closure, and you can’t do that – consider the following simpler case:
// 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
At some point, the variable passed in ceases to be x
and becomes a copy. This is probably happening at the point of capture.
edit: @rintaro’s answer nails it – inout
is not in fact semantically pass by reference
If you think about it this makes sense. What if you did this:
// 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()
When closures capture variables, they need to be created in memory in such a way that they can stay alive even after the scope they were declared ends. But in the case of f
above, it can’t do this – it’s too late to declare x
in this way, x
already exists. So I’m guessing it gets copied as part of the closure creation. That’s why incrementing the closure-captured version doesn’t actually increment x
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With