Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.7k views
in Technique[技术] by (71.8m points)

swift - Inout parameter in async callback does not work as expected

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

  1. at getOneUserApiData() call, the value of counter 0 copied to c1
  2. the closure captures c1
  3. call dataTaskWithRequest()
  4. getOneUserApiData returns, and the value of - unmodified - c1 is write-backed to counter
  5. repeat 1-4 procedure for c2, c3, c4 ...
  6. ... fetching from the Internet ...
  7. callback is called and c1 is incremented.
  8. callback is called and c2 is incremented.
  9. callback is called and c3 is incremented.
  10. callback is called and c4 is incremented.
  11. ...

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...