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

Posted

技术标签:

【中文标题】我可以在 Swift 中通过 NSURLSession 以某种方式执行同步 HTTP 请求吗【英文标题】:Can I somehow do a synchronous HTTP request via NSURLSession in Swift 【发布时间】: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()
        

但是同步请求呢?

【问题讨论】:

看看***.com/questions/21198404/… NSURLSession with NSBlockOperation and queues的可能重复 【参考方案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://***.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 步开始的代码看到它被允许继续。所以函数作为一个整体是同步的,即使里面发生了异步的东西。 换句话说,.distantFuturesemaphore 的超时时间,而不是网络请求的超时时间。【参考方案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 这样做的原因并告诉我们“正确的方法”或提供一些有用的教程或文档?谢谢

以上是关于我可以在 Swift 中通过 NSURLSession 以某种方式执行同步 HTTP 请求吗的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 中通过 WebView 下载 .pdf 或 .ppt

在 Swift 中通过 Multipeer Connectivity 临时同步两个 iOS 设备

在 Swift 中通过计数从多维数组中找到最大的数组

在 Xcode 中通过 Objective-C 代码测试 Swift 代码

在swift中通过inout参数编写一个var

在 Swift 中通过多个委托链接 UIButton 点击