How do I handle async requests in Swift?

2019-03-03 15:22发布

问题:

I make a web service call in one of my functions. I'm having trouble how to structure it so the logic is clean and not redundant. Here's what I have:

public func getTimes() -> [TimeName: MyResult] {
    let deferredTask: () -> [TimeName: MyResult] = {
        var computed = self.computeTimes()
        // Do something
        return computed
    }

    // Calculate timezone
    if let tz = timezone {
        timeZone = tz
        return deferredTask()
    } else {
        NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) {
            (data, response, error) in

            // Do something with data

            return deferredTask() // Getting a compiler error here btw: is not convertible to 'Void'
        }.resume()
    }
}

Then I call this using MyInstance.getTimes(). Something doesn't feel right though.. is setting up a deferredTask variable a good way to avoid redundant code?

Also, when I call this from another function, I would use var test = MyInstance.getTimes(). But this means test is in an async state and I can't use it in my other function until the web service is finished in getTimes.

What's a good way to handle this in Swift?

回答1:

As you rightly say, you cannot do this:

public func getTimes() -> [TimeName: MyResult] {
    let deferredTask: () -> [TimeName: MyResult] = {
        var computed = self.computeTimes()
        // Do something
        return computed
    }
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) {
            (data, response, error) in
            // Do something with data
            return deferredTask()
     }.resume()
}

The problem, as I think you are correctly trying to say, is that return deferredTask() is returned from the surrounding closure - not from getTimes. You cannot asynchronously return a value from a function call! That is the nature of asynchrony.

A typical standard solution is to write getTimes itself to accept a callback function as its parameter. It itself returns nothing. When the asynchronous method is finished, it calls the callback, thus getting the info back, at some future time, to the original caller.

To show you what I mean, I'm going to remove your deferredTask entirely from the story and just concentrate on the asynchronous task and the callback. So we would have this:

public func getTimes(f:(Any) -> Void) -> () {
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) {
            (data, response, error) in
            // ...
            f(whatever)
     }.resume()
}

The point of this architecture is that the call to f(whatever) effectively goes back to whoever handed us f in the first place - especially if that entity put self into it somewhere.