I guess I'll pick up where they left off, but in Swift since it's so many years later.
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let semaphore = DispatchSemaphore(value: 0)
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 10
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
// Redirects to google.com
guard let url = URL(string: "https://bit(dot)ly/19BiSHW") else {
return
}
var data: Data?
var response: URLResponse?
var error: Error?
let task = session.dataTask(with: url) { (innerData, innerResponse, innerError) in
// For clarity, we'll call this the data task's completion closure
// Pass the data back to the calling scope
data = innerData
response = innerResponse
error = innerError
semaphore.signal()
}
task.resume()
if semaphore.wait(timeout: .now() + .seconds(15)) == .timedOut {
// The task's completion closure wasn't called within the time out, handle appropriately
} else {
if let e = error as NSError? {
if e.domain == NSURLErrorDomain && e.code == NSURLErrorTimedOut {
print("The request timed out")
}
return
}
if let d = data {
// do whatever you want with the data here, such as print it
// (the data is the HTTP response body)
print(String.init(data: d, encoding: .utf8) ?? "Response data could not be turned into a string")
return
}
if let r = response {
print("No data and no error, but we received a response, we'll inspect the headers")
if let httpResponse = r as? HTTPURLResponse {
print(httpResponse.allHeaderFields)
}
}
}
}
}
extension ViewController: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Swift.Void) {
// Inside the delegate method, we will call the delegate's completion handler
// completionHandler: A block that your handler should call with
// either the value of the request parameter, a modified URL
// request object, or NULL to refuse the redirect and return
// the body of the redirect response.
// I found that calling the block with nil only triggers the
// return of the body of the redirect response if the session is ephemeral
// Calling this will trigger the data task's completion closure
// which signals the semaphore and allows execution to continue
completionHandler(nil)
}
}
What the code is doing:
It is creating an inherently asynchronous task (URLSessionTask), telling it to being execution by calling resume()
, then halting the current execution context by waiting on a DispatchSemaphore. This is trick I've seen used, and personally used on many occasions to make something asynchronous behave in a synchronous fashion.
The key point to make is that the code stops execution in the current context. In this example, that context is the main thread (since it is in a UIViewController method), which is generally bad practice. So, if your synchronous code never continues executing (because the semaphore is never signaled) then you UI thread will be stopped forever causing the UI to be frozen.
The final piece is the implementation of the delegate method. The comments suggest that calling completionHandler(nil)
should suffice and the documentation supports that. I found that this is only sufficient if you have an ephemeral
URLSessionConfiguration. If you have the default configuration, the data task's completion closure doesn't get invoked, so the semaphore never gets signaled, therefore the code to never moves forward. This is what was causing the commenter's/asker's problems of a frozen UI.