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

时间:2014-11-06 16:22:31

标签: http swift httprequest nsurl

我可以通过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()
        }

但同步请求呢?

提前致谢。

6 个答案:

答案 0 :(得分:65)

您可以使用此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)
    }
}

答案 1 :(得分:35)

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回答“爱斯基摩人!” Apple Developer Relations,开发人员   技术支持,核心操作系统/硬件

答案 2 :(得分:13)

更新了其中一个使用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.")
}

答案 3 :(得分:2)

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

使用示例 1:

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()

答案 4 :(得分:-1)

来自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)

答案 5 :(得分:-3)

小心同步请求,因为它可能导致糟糕的用户体验,但我知道有时它是必要的。 对于同步请求,请使用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
    }