ios - 如何在 Swift 中处理异步 http 请求

标签 ios swift

我有以下代码:

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

看来 print(bus) 在收到 http 响应并填充总线数组之前正在运行。我的目标是用 http 响应数据填充总线数组,然后打印它。我不知道如何实现这一点。

最佳答案

一些观察:

  1. 您应该将此网络代码拉入其自己的方法中。
  2. 您在正文和 URL 中指定请求。 body 就够了。从 URL 中删除它。
  3. 您应该删除 NSMutableData (使用Data),NSMutableURLRequest (使用URLRequest),NSURL (使用URL),NSArray , NSDictionary等等。通常最好尽可能保留 Swift 类型。
  4. 而不是手动迭代JSONSerialization结果,使用JSONDecoder .
  5. 您还需要对请求正文进行百分比编码,如 bus_stop=Science Hillx-www-form-urlrequest 中无效。请参阅https://stackoverflow.com/a/26365148/1271826 .
  6. 你说:

    My goal would be to fill the bus array with the http response data, then print it

    您需要根据完成处理程序闭包内的请求移动代码。

因此:

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

关于ios - 如何在 Swift 中处理异步 http 请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60458251/

相关文章:

ios - iOS部署需要Flash CS5.5吗

ios - 无法提交 iOS 应用程序以供审核 - "There are one or more errors on the page."

swift - 使用 UIBezierPath 的带有圆角的 TableView 页眉和页脚

swift - 命令因信号 : Abort trap: 6 而失败

ios - iOS 和 PC 之间的安全信号

ios - 按钮点击事件不触发

ios - 由于内存需求,CIDepthBlurEffect 无法渲染部分图像

ios - 将 NSNumber 转换为 Float 时出现意外行为

swift - (Swift) 如何更改单元格外的标签文本(事件发生的地方)?

ios - 未调用 SDWebImage 下载图像完成 block