【问题标题】:Can I somehow do a synchronous HTTP request via NSURLSession in Swift我可以在 Swift 中通过 NSURLSession 以某种方式执行同步 HTTP 请求吗
【发布时间】:2015-01-03 05:53:34
【问题描述】:

我可以通过 Swift 中的NSURLSession 以某种方式执行同步 HTTP 请求吗?

我可以通过以下代码进行异步请求:

if let url = NSURL(string: "https://2ch.hk/b/threads.json") {
            let task = NSURLSession.sharedSession().dataTaskWithURL(url) {
                (data, response, error) in

                var jsonError: NSError?
                let jsonDict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as [String: AnyObject]
                if jsonError != nil {
                    return
                }

                // ...
            }
            task.resume()
        }

但是同步请求呢?

【问题讨论】:

标签: http swift httprequest nsurl


【解决方案1】:

我想使用DispatchGroup 提供更现代的解决方案。

使用示例一:

var urlRequest = URLRequest(url: config.pullUpdatesURL)
urlRequest.httpMethod = "GET"
urlRequest.httpBody = requestData
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

let (data, response, error) = URLSession.shared.syncRequest(with: urlRequest)

用法示例2:

let url = URL(string: "https://www.google.com/")
let (data, response, error) = URLSession.shared.syncRequest(with: url)

扩展代码:

extension URLSession {
   
   func syncRequest(with url: URL) -> (Data?, URLResponse?, Error?) {
      var data: Data?
      var response: URLResponse?
      var error: Error?
      
      let dispatchGroup = DispatchGroup()
      let task = dataTask(with: url) {
         data = $0
         response = $1
         error = $2
         dispatchGroup.leave()
      }
      dispatchGroup.enter()
      task.resume()
      dispatchGroup.wait()
      
      return (data, response, error)
   }
   
   func syncRequest(with request: URLRequest) -> (Data?, URLResponse?, Error?) {
      var data: Data?
      var response: URLResponse?
      var error: Error?
      
      let dispatchGroup = DispatchGroup()
      let task = dataTask(with: request) {
         data = $0
         response = $1
         error = $2
         dispatchGroup.leave()
      }
      dispatchGroup.enter()
      task.resume()
      dispatchGroup.wait()
      
      return (data, response, error)
   }
   
}

作为奖励,如果需要,您可以轻松实现超时。为此,您需要使用

func wait(timeout: DispatchTime) -> DispatchTimeoutResult 而不是 func wait()

【讨论】:

    【解决方案2】:

    来自https://stackoverflow.com/a/58392835/246776的重复回答


    如果基于信号量的方法不适合您,请尝试基于轮询的方法。

    var reply = Data()
    /// We need to make a session object.
    /// This is key to make this work. This won't work with shared session.
    let conf = URLSessionConfiguration.ephemeral
    let sess = URLSession(configuration: conf)
    let task = sess.dataTask(with: u) { data, _, _ in
        reply = data ?? Data()
    }
    task.resume()
    while task.state != .completed {
        Thread.sleep(forTimeInterval: 0.1)
    }
    FileHandle.standardOutput.write(reply)
    

    【讨论】:

      【解决方案3】:

      更新了一个答案以使用 URLRequest 代替,因此我们可以使用 PUT 等代替。

      extension URLSession {
          func synchronousDataTask(urlrequest: URLRequest) -> (data: Data?, response: URLResponse?, error: Error?) {
              var data: Data?
              var response: URLResponse?
              var error: Error?
      
              let semaphore = DispatchSemaphore(value: 0)
      
              let dataTask = self.dataTask(with: urlrequest) {
                  data = $0
                  response = $1
                  error = $2
      
                  semaphore.signal()
              }
              dataTask.resume()
      
              _ = semaphore.wait(timeout: .distantFuture)
      
              return (data, response, error)
          }
      }
      

      我是这样打电话的。

      var request = URLRequest(url: url1)
      request.httpBody = body
      request.httpMethod = "PUT"
      let (_, _, error) = URLSession.shared.synchronousDataTask(urlrequest: request)
      if let error = error {
          print("Synchronous task ended with error: \(error)")
      }
      else {
          print("Synchronous task ended without errors.")
      }
      

      【讨论】:

      • 使用.distantFuture 有什么特别的原因吗?在我看来,比请求超时稍大一点的东西会更有意义......没有人喜欢无限等待。
      • @AbhiBeckert 如果我理解正确——我想我明白了——它的工作方式是这样的: 1. 信号量设置为 0。 2. 异步dataTask 被设置和调用。 3. 我们到达 semaphore.wait() 调用。因为此时允许还有 0 个线程继续运行,所以我们等待。 4. 在未来的某个时刻,异步 dataTask 完成。在其完成闭包中,我们调用 semaphore.signal(),所以... 5. 我们从第 3 步开始的代码看到它被允许继续。所以函数作为一个整体是同步的,即使里面发生了异步的东西。
      • 换句话说,.distantFuture 是 semaphore 的超时时间,而不是网络请求的超时时间。
      【解决方案4】:

      您可以使用这个 NSURLSession 扩展来添加同步方法:

      extension NSURLSession {
          func synchronousDataTaskWithURL(url: NSURL) -> (NSData?, NSURLResponse?, NSError?) {
              var data: NSData?, response: NSURLResponse?, error: NSError?
      
              let semaphore = dispatch_semaphore_create(0)
      
              dataTaskWithURL(url) {
                  data = $0; response = $1; error = $2
                  dispatch_semaphore_signal(semaphore)
              }.resume()
      
              dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
      
              return (data, response, error)
          }
      }
      

      Swift 3 更新:

      extension URLSession {
          func synchronousDataTask(with url: URL) -> (Data?, URLResponse?, Error?) {
              var data: Data?
              var response: URLResponse?
              var error: Error?
      
              let semaphore = DispatchSemaphore(value: 0)
      
              let dataTask = self.dataTask(with: url) {
                  data = $0
                  response = $1
                  error = $2
      
                  semaphore.signal()
              }
              dataTask.resume()
      
              _ = semaphore.wait(timeout: .distantFuture)
      
              return (data, response, error)
          }
      }
      

      【讨论】:

      • 我将此扩展名添加到我的文件底部并将NSURLSession.sharedSession().dataTaskWithURL(url) 替换为NSURLSession.sharedSession().synchronousDataTaskWithURL(url),但我收到一个错误,即调用中有一个额外的参数。调用此扩展函数的正确方法是什么?
      • 你写的是正确的调用方式,可能错误在其他地方。你能提供更多背景信息吗?
      • 额外的参数是完成处理程序,在同步调用中不再需要参数化。调用此方法应如下所示(假设您已声明所需的变量):(data, response, error) = NSURLSession.sharedSession().synchronousDataTaskWithURL(url)
      • 这些是 completionHandler 回调的参数。见developer.apple.com/library/ios/documentation/Foundation/…:
      • 当我在自己的解决方案中实现它时,我的完成处理程序永远不会被调用。当我打开 CFNetworkingDiagnostics 时,我可以看到 URL 请求成功完成,但我的处理程序从未执行。任何人都经历过这种情况或对解决有任何指导吗?谢谢。
      【解决方案5】:

      Apple thread discussing the same issue.

      + (NSData *)sendSynchronousRequest:(NSURLRequest *)request  
          returningResponse:(__autoreleasing NSURLResponse **)responsePtr  
          error:(__autoreleasing NSError **)errorPtr {  
          dispatch_semaphore_t    sem;  
          __block NSData *        result;  
      
          result = nil;  
      
          sem = dispatch_semaphore_create(0);  
      
          [[[NSURLSession sharedSession] dataTaskWithRequest:request  
              completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {  
              if (errorPtr != NULL) {  
                  *errorPtr = error;  
              }  
              if (responsePtr != NULL) {  
                  *responsePtr = response;  
              }  
              if (error == nil) {  
                  result = data;  
              }  
              dispatch_semaphore_signal(sem);  
          }] resume];  
      
          dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);  
      
         return result;  
      }  
      

      Quinn 的回答“爱斯基摩人!”苹果开发者关系,开发者 技术支持、核心操作系统/硬件

      【讨论】:

      • 如果我需要在两个异步队列中使用相同的 NSURLSession 实例进行同步请求,dataTasWithRequest 是否会从内部异步执行?
      【解决方案6】:

      小心同步请求,因为它可能会导致糟糕的用户体验,但我知道有时这是必要的。 对于同步请求,使用 NSURLConnection:

      func synchronousRequest() -> NSDictionary {
      
              //creating the request
              let url: NSURL! = NSURL(string: "exampledomain/...")
              var request = NSMutableURLRequest(URL: url)
              request.HTTPMethod = "GET"
              request.addValue("application/json", forHTTPHeaderField: "Content-Type")
      
      
              var error: NSError?
      
              var response: NSURLResponse?
      
              let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
      
              error = nil
              let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
      
              return resultDictionary
          }
      

      【讨论】:

      • 这不再适用于 Swift 2。 Apple 显然决定我们不需要同步请求。
      • @SpaceDog 谢谢你的评论。您能否解释一下 Apple 这样做的原因并告诉我们“正确的方法”或提供一些有用的教程或文档?谢谢
      猜你喜欢
      • 1970-01-01
      • 2014-09-28
      • 2017-04-18
      • 2012-05-19
      • 2019-05-08
      • 1970-01-01
      • 2015-07-05
      • 1970-01-01
      • 2023-03-18
      相关资源
      最近更新 更多