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 of counter
0
copied to c
1
- the closure captures
c
1
- call
dataTaskWithRequest()
getOneUserApiData
returns, and the value of - unmodified - c
1 is write-backed to counter
- 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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…