我可以在 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 步开始的代码看到它被允许继续。所以函数作为一个整体是同步的,即使里面发生了异步的东西。
换句话说,.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 这样做的原因并告诉我们“正确的方法”或提供一些有用的教程或文档?谢谢以上是关于我可以在 Swift 中通过 NSURLSession 以某种方式执行同步 HTTP 请求吗的主要内容,如果未能解决你的问题,请参考以下文章
在 Swift 中通过 WebView 下载 .pdf 或 .ppt
在 Swift 中通过 Multipeer Connectivity 临时同步两个 iOS 设备