Swift 上的同步 URL 请求 2

Synchronous URL request on Swift 2

我从 here 获得了这段代码,用于在 Swift 上同步请求 URL 2.

  func send(url: String, f: (String)-> ()) {
    var request = NSURLRequest(URL: NSURL(string: url)!)
    var response: NSURLResponse?
    var error: NSErrorPointer = nil
    var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: error)
    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
  }

但函数 NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: error) 已被弃用,我看不出如何在 Swift 上执行同步请求,因为替代方案是异步的。显然 Apple 弃用了唯一可以同步执行此操作的功能。

我该怎么做?

弃用是有原因的 - 它没有用处。您应该避免将同步网络请求视为瘟疫。它有两个主要问题,只有一个优点(它很容易使用..但不是异步的吗?):

  • 如果不是从不同线程调用,请求会阻塞您的 UI,但如果您这样做,为什么不立即使用异步处理程序?
  • 除非它自己出错,否则无法取消该请求

不用这个,只用异步请求:

NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in

    // Handle incoming data like you would in synchronous request
    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
})

iOS9 弃用

由于在 iOS9 中此方法已被弃用,我建议您改用 NSURLSession:

let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request) { (data, response, error) -> Void in

    // Handle incoming data like you would in synchronous request
    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
}

如果您真的想要同步进行,您可以随时使用信号量:

func send(url: String, f: (String) -> Void) {
    var request = NSURLRequest(URL: NSURL(string: url)!)
    var error: NSErrorPointer = nil
    var data: NSData

    var semaphore = dispatch_semaphore_create(0)

    try! NSURLSession.sharedSession().dataTaskWithRequest(request) { (responseData, _, _) -> Void in
        data = responseData! //treat optionals properly
        dispatch_semaphore_signal(semaphore)
    }.resume()

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
}

编辑: 添加一些骇人听闻的内容!所以代码有效,不要在生产代码中这样做

Swift 3.0+ (3.0, 3.1, 3.2, 4.0)

func send(url: String, f: (String) -> Void) {
    guard let url = URL(string: url) else {
        print("Error! Invalid URL!") //Do something else
        return
    }

    let request = URLRequest(url: url)
    let semaphore = DispatchSemaphore(value: 0)

    var data: Data? = nil

    URLSession.shared.dataTask(with: request) { (responseData, _, _) -> Void in
        data = responseData
        semaphore.signal()
    }.resume()

    semaphore.wait(timeout: .distantFuture)

    let reply = data.flatMap { String(data: [=11=], encoding: .utf8) } ?? ""
    f(reply)
}

同步请求有时在后台线程上运行良好。有时你有一个复杂的、不可能更改的充满异步请求的代码库,等等。然后有一个小请求不能作为异步折叠到当前系统中。如果同步失败,则您将得不到任何数据。简单的。它模仿文件系统的工作方式。

当然它没有涵盖所有类型的可能性,但是异步也有很多没有涵盖的可能性。

基于@fpg1503 的回答,我在 Swift 3 中做了一个简单的扩展:

extension URLSession {

    func synchronousDataTask(with request: URLRequest) throws -> (data: Data?, response: HTTPURLResponse?) {

        let semaphore = DispatchSemaphore(value: 0)

        var responseData: Data?
        var theResponse: URLResponse?
        var theError: Error?

        dataTask(with: request) { (data, response, error) -> Void in

            responseData = data
            theResponse = response
            theError = error

            semaphore.signal()

        }.resume()

        _ = semaphore.wait(timeout: .distantFuture)

        if let error = theError {
            throw error
        }

        return (data: responseData, response: theResponse as! HTTPURLResponse?)

    }

}

然后您只需调用:

let (data, response) = try URLSession.shared.synchronousDataTask(with: request)

这可能会“卷曲”您的脚趾,但有时您只想:

  • 跳过错误处理
  • 真正做到同步,就像在测试中一样
try! String(contentsOf: URL(string: "https://google.com")!)