1

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?

Raphael
  • 9,779
  • 5
  • 63
  • 94
  • [Closely related question](http://stackoverflow.com/questions/26784315/can-i-somehow-do-a-synchronous-http-request-via-nsurlsession-in-swift). – Raphael Feb 24 '17 at 11:16

3 Answers3

1

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()
Raphael
  • 9,779
  • 5
  • 63
  • 94
1

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()
Raphael
  • 9,779
  • 5
  • 63
  • 94
  • 1
    Would the downvoter please explain why they think this is a bad solution, and ideally present a better alternative? – Raphael Feb 28 '17 at 17:16
  • This has problems if the asynchronous task sends something to the main queue (or whichever queue `sem.wait()` blocks) before `sem.signal()`; then we have a deadlock, as seen [here](http://stackoverflow.com/q/42644111/539599). – Raphael May 17 '17 at 13:34
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
Raphael
  • 9,779
  • 5
  • 63
  • 94