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


71

我可以NSURLSession在Swift中通过某种方式执行同步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()
        }

但是同步请求呢?



Answers:


73

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

2
我将此扩展名添加到了文件的底部,并替换NSURLSession.sharedSession().dataTaskWithURL(url)NSURLSession.sharedSession().synchronousDataTaskWithURL(url),但是我收到一个错误,即调用中有一个额外的参数。调用此扩展功能的正确方法是什么?
tylerSF '16

2
您写的是调用它的正确方法,可能错误在其他地方。您能否提供更多背景信息?
Nick Keets '16

2
额外的参数是完成处理程序,不再需要在同步调用中对其进行参数化。调用此方法应如下所示(假设您已经声明了必需的变量):(data, response, error) = NSURLSession.sharedSession().synchronousDataTaskWithURL(url)
dave

1
这些是complementHandler回调的参数。见developer.apple.com/library/ios/documentation/Foundation/...
尼克珍珠鸡

2
当我在自己的解决方案中实现此功能时,我的完成处理程序将永远不会被调用。当我打开CFNetworkingDiagnostics时,我可以看到URL请求成功完成,但是我的处理程序从未执行。是否有人也有这种经历或对解决有任何指导?谢谢。
JonnyB

41

苹果线程讨论了同样的问题。

+ (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;  
}  

奎因回答“爱斯基摩人!” Apple开发人员关系,开发人员技术支持,核心操作系统/硬件


如果我需要使用NSURLSession的相同实例在两个异步队列中进行同步请求,那么dataTasWithRequest是否将从内部异步执行?
Eugene Biryukov

20

更新了答案之一,改用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吗?在我看来,比请求超时大一点的东西更有意义……没人喜欢无限等待。
Abhi Beckert

-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
    }

2
这不再适用于Swift 2。苹果显然决定我们不需要同步请求。
鸭子

1
@SpaceDog谢谢您的评论。您能否解释一下Apple为什么这样做,并告诉我们“正确的方法”,或者提供一些有用的教程或文档?谢谢
ea

-3

来自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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.