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
- at
getOneUserApiData()
call, the value ofcounter
0
copied toc
1 - the closure captures
c
1 - call
dataTaskWithRequest()
getOneUserApiData
returns, and the value of – unmodified –c
1 is write-backed tocounter
- repeat 1-4 procedure for
c
2,c
3,c
4 … - … fetching from the Internet …
- callback is called and
c
1 is incremented. - callback is called and
c
2 is incremented. - callback is called and
c
3 is incremented. - callback is called and
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.