如何解决发出循环的HTTP请求
我正在编写与在线翻译API连接的翻译器应用程序。由于API的大小限制,我编写了程序来一次发送一个句子的文本,然后将翻译内容合并在一起。我已经循环播放
let serialQueue = DispatchQueue(label: "translationQueue")
for line in lines {
serialQueue.async {
print("line is: " + line)
var jpText = String(line)
if jpText.isEmpty {
jpText = "\n"
}
let escapedStr = jpText.addingPercentEncoding(withAllowedCharacters: (NSCharacterSet.urlQueryAllowed))
let urlStr:String = ("https://api.mymemory.translated.net/get?q="+escapedStr!+"&langpair="+langStr!)
let url = URL(string: urlStr)
// Creating Http Request
let request = NSURLRequest(url: url!)
// If empty,don't feed to translator.
if escapedStr!.isEmpty {
//translatedLines.append("\n")
self.enTextView.text = translatedLines
}
else {
let configuration = URLSessionConfiguration.default
configuration.waitsForConnectivity = true
let defaultSession = URLSession(configuration: configuration,delegate: self,delegateQueue: nil)
var dataTask: URLSessionDataTask?
let group = DispatchGroup()
group.enter()
dataTask?.cancel()
dataTask = defaultSession.dataTask(with: request as URLRequest) { [weak self] data,response,error in
if let error = error {
// self?.errorMessage += "DataTask error: " + error.localizedDescription + "\n"
print("DataTask error: " + error.localizedDescription + "\n")
} else if
let data = data,let response = response as? HTTPURLResponse,response.statusCode == 200 {
let jsonDict: NSDictionary!=((try! JSONSerialization.jsonObject(with: data,options: JSONSerialization.ReadingOptions.mutableContainers)) as! NSDictionary)
// if(jsonDict.value(forKey: "responseStatus") as! NSNumber == 200){
let responseData: NSDictionary = jsonDict.object(forKey: "responseData") as! NSDictionary
group.leave()
var translatedString = String()
translatedString = responseData.object(forKey: "translatedText") as! String
let data = translatedString.data(using: .utf8)
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,.characterEncoding: String.Encoding.utf8.rawValue
]
guard let attributedString = try? NSAttributedString(data: data!,options: options,documentAttributes: .none) else {
return
}
let decodedString = attributedString.string
print("decoded: " + decodedString)
translatedLines.append(decodedString)
translatedLines.append("\n")
DispatchQueue.main.async {
self?.enTextView.text = translatedLines
}
}
}
dataTask?.resume()
group.wait()
}
}
}
但是翻译输出是随机排列的。我大致了解,正在发送并发请求。但是我可以在for循环中做什么以确保整个发送/接收都发生在移至下一个迭代之前?
解决方法
我将使用以下技术:
- 为翻译结果预分配一个数组。结果可能是成功(带有关联的翻译文本)或失败(带有关联的错误类型)。 Swift的
Result
类型在此用例中非常方便。 - 为每个数据任务分配一个索引(实际上是在步骤1中创建的数组中的索引)。
- 同时执行所有数据任务,并使用
DispatchGroup
进行协调。 - 成功完成或失败完成后,每个任务会将其结果写入数组。
- 所有数据任务完成后,调度组将通知主队列并执行我们的完成块,在此我们可以更新UI。
下面是一个根据您的大致松散的快速操场代码,该代码使用了我上面描述的技术:
let lines = ["one","two","three","four"]
enum TranslationError: Error {
case neverRequested
case countNotBuildUrl
case dataTaskError(localizedDescription: String)
case responseDidNotContainData
case parsingError
}
let dispatchGroup = DispatchGroup()
var translationResults = [Result<String,TranslationError>](repeating: .failure(.neverRequested),count: lines.count)
for i in 0..<lines.count {
dispatchGroup.enter()
let line = lines[i]
var urlComponents = URLComponents(string: "https://api.mymemory.translated.net/get")!
urlComponents.queryItems = [
URLQueryItem(name: "q",value: line),URLQueryItem(name: "langpair",value: "en|fr")
]
guard let url = urlComponents.url else {
translationResults[i] = .failure(.countNotBuildUrl)
dispatchGroup.leave()
continue
}
let dataTask = URLSession.shared.dataTask(with: url) { data,_,error in
print("Data task #\(i) finished.")
defer {
dispatchGroup.leave()
}
if let error = error {
DispatchQueue.main.async {
translationResults[i] = .failure(.dataTaskError(localizedDescription: error.localizedDescription))
}
return
}
guard let data = data else {
DispatchQueue.main.async {
translationResults[i] = .failure(.responseDidNotContainData)
}
return
}
guard let jsonDict = try? JSONSerialization.jsonObject(with: data,options: []) as? NSDictionary,let responseData = jsonDict.value(forKey: "responseData") as? NSDictionary,let translatedLine = responseData.value(forKey: "translatedText") as? String else
{
DispatchQueue.main.async {
translationResults[i] = .failure(.parsingError)
}
return
}
DispatchQueue.main.async {
translationResults[i] = .success(translatedLine)
}
}
print("Starting data task #\(i)...")
dataTask.resume()
}
dispatchGroup.notify(queue: .main,execute: {
// Handle errors and update the UI
print(translationResults)
})
一些注意事项:
- 所有数据任务完成块更新同一队列上的
translationResults
数组很重要,这就是为什么我每次都要调用DispatchQueue.main.async
的原因。不这样做可能会导致奇怪的崩溃,因为数组在Swift中不是线程安全的。 - 该技术不允许随着翻译的进行逐行增量更新UI,但是可以添加类似的内容。
- 在真实的应用程序中,我将重构此代码并将其拆分为职责更集中的几种方法。但是,为了更容易理解这个想法,我决定不这样做。
- 代码中的错误处理也是非常基本的,仅用于显示想法并保持其显示状态,并且不包括我在实际应用中所做的其他检查。
- 我很确定有更好和更优雅的方法来实现相同的结果,因此我很期待看到有人提出更好的实施方案。