我想我会从他们离开的地方继续,但在 Swift 中,因为它已经过了很多年了。
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)
}
}
代码在做什么:
它正在创建一个固有的异步任务(URLSessionTask),通过调用告诉它正在执行resume()
,然后通过等待 DispatchSemaphore 来停止当前的执行上下文。这是我见过的技巧,并且在很多场合都亲自使用过,以使异步行为以同步方式运行。
要说明的关键点是代码在当前上下文中停止执行。在此示例中,该上下文是主线程(因为它在 UIViewController 方法中),这通常是不好的做法。因此,如果您的同步代码永远不会继续执行(因为信号量永远不会发出信号),那么您的 UI 线程将永远停止,从而导致 UI 被冻结。
最后一部分是委托方法的实现。评论表明调用completionHandler(nil)
就足够了,并且文档支持这一点。我发现这仅在您有ephemeral
URLSessionConfiguration 时才足够。如果您有默认配置,则不会调用数据任务的完成闭包,因此信号量永远不会收到信号,因此代码永远不会向前移动。这就是导致评论者/提问者出现冻结 UI 问题的原因。