ios - 如何在 Swift 中处理异步 http 请求
问题描述
我有以下代码:
func mapView(_ mapView: MGLMapView, annotation: MGLAnnotation, calloutAccessoryControlTapped control: UIControl) {
var bus = [String]()
let headers = [
"content-type": "application/x-www-form-urlencoded",
"cache-control": "no-cache",
"postman-token": "23cb4108-e24b-adab-b979-e37fd8f78622"
]
let postData = NSMutableData(data: "bus_stop=Science Hill".data(using: String.Encoding.utf8)!)
let request = NSMutableURLRequest(url: NSURL(string: "https://ucsc-bts3.soe.ucsc.edu/bus_stops/inner_eta.php?%22bus_stop%22=%22Science%20Hill%22")! as URL,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error!)
} else {
_ = response as? HTTPURLResponse
}
do {
let jsonObject = try JSONSerialization.jsonObject(with: data!)
guard let jsonArray = jsonObject as? [String: Any] else{
print("JsonSerialization Failed")
return
}
if let etaTableRows = jsonArray["rows"] as? NSArray{
for etaData in etaTableRows{
let etaDictionary = etaData as? NSDictionary
bus.append(etaDictionary!["bus_type"] as! String)
}
}
} catch {
print("JSONSerialization error:", error)
}
})
dataTask.resume()
print(bus) //bus array should be updated here
mapView.deselectAnnotation(annotation, animated: false)
let schedule = ScheduleVC()
schedule.data.append(annotation.title!! + " ETAs")
self.present(schedule, animated: true, completion: nil)
}
在接收到 http 响应并填充总线数组之前,似乎正在运行 print(bus)。我的目标是用 http 响应数据填充总线数组,然后打印它。我不确定如何做到这一点。
解决方案
几点观察:
- 您应该将此网络代码提取到它自己的方法中。
- 您在正文和 URL 中都指定了请求。身体就够了。从 URL 中删除它。
- 您应该删除
NSMutableData
(useData
)、NSMutableURLRequest
(useURLRequest
)、NSURL
(useURL
)、NSArray
、NSDictionary
等。通常最好尽可能使用 Swift 类型。 JSONSerialization
与其手动迭代结果,不如使用JSONDecoder
.- 您还需要对请求的正文进行百分比编码,因为
bus_stop=Science Hill
在x-www-form-urlrequest
. 请参阅https://stackoverflow.com/a/26365148/1271826。 你说:
我的目标是用 http 响应数据填充总线数组,然后打印它
您需要根据完成处理程序闭包中的请求移动代码。
因此:
func fetchBuses(completion: @escaping (Result<[Bus], Error>) -> Void) {
let headers = [
"content-type": "application/x-www-form-urlencoded",
"cache-control": "no-cache",
"postman-token": "23cb4108-e24b-adab-b979-e37fd8f78622"
]
let url = URL(string: "https://ucsc-bts3.soe.ucsc.edu/bus_stops/inner_eta.php")!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10)
request.httpBody = ["bus_stop": "Science Hill"].percentEncoded()
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { data, response, error in
guard
error == nil,
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
completion(.failure(error ?? BusError.unknown(data, response)))
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let responseObject = try decoder.decode(ResponseObject.self, from: responseData)
completion(.success(responseObject.rows))
} catch let jsonError {
completion(.failure(jsonError))
}
}
dataTask.resume()
}
和
func mapView(_ mapView: MGLMapView, annotation: MGLAnnotation, calloutAccessoryControlTapped control: UIControl) {
fetchBuses { result in
switch result {
case .failure(let error):
print(error)
case .success(let buses):
print(buses)
DispatchQueue.main.async {
mapView.deselectAnnotation(annotation, animated: false)
let schedule = ScheduleVC()
schedule.data.append(annotation.title!! + " ETAs")
self.present(schedule, animated: true, completion: nil)
}
}
}
}
在哪里
enum BusError: Error {
case unknown(Data?, URLResponse?)
}
struct Bus: Decodable {
let busId: Int
let busType: String
let nextBusStop: String
let timeAway: Int
}
struct ResponseObject: Decodable {
let rows: [Bus]
}
和
extension Dictionary {
func percentEncoded() -> Data? {
return map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)
return allowed
}()
}
推荐阅读
- datetime - 谷歌脚本:弄清楚如何比较时间 B 和时间 C 之间的时间 A
- javascript - 通过底部激活/停用字段(HTML + Javascript)
- python - jupyter notebook和google colaboratory之间的LSTM性能差异
- javascript - 我在使用“更改”事件时遇到问题。我能做些什么?我可以使用什么活动?
- javascript - 谷歌表格自动按颜色排序范围
- javascript - 如何在 javascript 网站中使用 OpenTripPlanner?
- hadoop - 当数据从 hive 导出到 hadoop 边缘节点上的 csv 时,记录行数增加
- node.js - express 如何在没有 next() 的异步代码中捕获抛出的错误?
- delphi - 显示在包含 tchart 的面板顶部的 tPanel 是透明的(Delphi)
- r - 没有标签或轴标题 - WaveleComp 包的 wc.image 问题(?)