How can we wait for HTTP requests to finish?

2020-03-31 05:10发布

问题:

Using several answers on SO, we have managed to write and execute a basic HTTP request:

import Foundation

let url:URL = URL(string: "http://jsonplaceholder.typicode.com/posts")!
let session = URLSession.shared

var request = URLRequest(url: url)
request.httpMethod = "POST"
let paramString = "data=Hello"
request.httpBody = paramString.data(using: String.Encoding.utf8)

let task = session.dataTask(with: request as URLRequest) {
    (data, response, error) in

    guard let data = data, let _:URLResponse = response, error == nil else {
        print("error")
        return
    }

    let dataString: String =  String(data: data, encoding: String.Encoding.utf8)!
    print("here")

    print("Data: \(dataString)")
    print("Response: \(response!)")
}

task.resume()
while task.response == nil {}
print("Done")

You'll note that we already busy-wait until task.response is set. However, neither data nor response are printed, only here.

After endless trials with wrapping things this or that way we determine that we have a Heisenbug here: changing nothing in the code, sometimes here is printed, sometimes nothing, and very, very rarely dataString (let alone response).

So we insert sleep(3) before print("Done") and, wonder of wonders, we get all prints.

Then we yelled a little bit (I may actually have thrown something), thought briefly about abandoning Swift altogether, but then calmed down enough to facepalm like sirs and post here.

Apparently, the main thread terminates whether or not any asynchronous tasks (threads?) are still running or not, killing all its spawn. How can we prevent that from happening, that is "join" the threads?

Bonus question: Does Alamofire deal with this behind the covers?

回答1:

Active waiting seems to be the only way on the GCD. Using standard library material, this is what works:

import Foundation

<...>

var done = false

let task = session.dataTask(with: request as URLRequest) {
    (data, response, error) in

    <...>
    done = true
}

task.resume()

repeat {
    RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1))
} while !done


回答2:

Using CwUtils by Matt Gallagher, I implemented a simple CountdownLatch which does the job:

import Foundation
import CwlUtils

<...>

let task = session.dataTask(with: request as URLRequest) {
    (data, response, error) in

    <...>
    latch.countDown()
}

task.resume()
latch.await()


回答3:

The most straight-forward (and built-in) way is probably to use a DispatchSemaphore:

<...>

let sem = DispatchSemaphore(value: 0)

let task = session.dataTask(with: request as URLRequest) {
    (data, response, error) in

    <...>
    sem.signal()
}

task.resume()
sem.wait()