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()