我可以以某种方式做一个同步的 HTTP 请求通过在 Swift 的 NSULSession

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

但是同步请求怎么办呢?

49555 次浏览

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

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
}

苹果也在讨论同样的问题。

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

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

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

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

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

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

我想提供一个更现代的解决方案使用 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()