[SwiftUI 100 天] Cupcake Corner - part6 网络请求

3,933 阅读5分钟
译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀

在网络上发送和接收订单

对于处理网络请求,iOS 内建了很棒的功能,尤其是URLSession类使得发送和接收数据变得相当简单。结合Codable,我们在 Swift 对象和 JSON 之间互相转换,加上URLRequest,使得我们能够准确地配置要发送的数据,我们可以用少于 20 行的代码完成很酷的事情。

首先,我们创建一个方法,在 Place Order 按钮被点击时调用 —— 把下面这个方法添加到CheckoutView

func placeOrder() {
}

然后把 Place Order 按钮改成这样:

Button("Place Order") {
    self.placeOrder()
}
.padding()

placeOrder()方法里,我们需要做三件事:

  1. 把当前的 order 对象转换成 JSON 数据,以便发送
  2. 准确一个 URLRequest ,用来编码后的 JSON 数据
  3. 运行请求,处理响应

第一步很直接,因为我们已经让Order类遵循Codable协议,所以我们可以用JSONEncoder来归档它,把下面的代码添加到placeOrder()

guard let encoded = try? JSONEncoder().encode(order) else {
    print("Failed to encode order")
    return
}

第二步 —— 准备URLRequest以发送我们的数据 —— 需要更多的思考。想想看,我们需要将数据一种特定的方式发给服务器,以便它能正确处理,这意味着我们需要在订单之外提供两个额外的字段:

  1. 决定数据如何发送的 HTTP 方法。有几种 HTTP 方法,实践上只有 GET (“我想要读取数据”) 和 POST (“我想要写入数据”) 最为常用。这里我们是想写数据,因为我们用 POST 方法。
  2. 请求的内容类型决定了被发送的数据的种类,这会影响服务端对待数据的方式。这一点是 MIME 类型来确定的,最初被设计用于发送电子邮件的附件,它有非常多的选项。

因此,接下来placeOrder()的代码是创建一个URLRequest,并且配置它以 HTTP POST 方法来发送 JSON 数据,并且附上我们的数据。

当然,实际的问题是,我们要往哪里发送我们的请求,我想我们不会为了这个教程而去搭建你自己的 web 服务器。因此,我们会用到一个很有用的网站,叫做reqres.in,它允许我们发送任意的数据,然后自动发送回来。对于网络功能的原型,这种方法很棒,因为你会基于你发送的任何数据,获得实际的数据反馈。

把下面的代码添加到placeOrder()

let url = URL(string: "https://reqres.in/api/cupcakes")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = encoded

注意,这里我给URL(string:)构造器添加了一个强制解包。基于字符串创建 URL 是有可能失败的,因为你可能传入了一个不靠谱的 URL 字符串,但这里我手打的 URL 是可以被确信是正确的 —— 这里头没有字符串插值可能导致问题。

到这里,网络请求已经设置好,我们要把它用于URLSession.shared.dataTask()和我们刚刚创建的 URL 请求。记住,如果你忘记在dataTask上调用resume(),它不会启动。这也正是为什么我每次写 task 时,总是在填充方法体之前提前写好resume调用的原因。

接下来,把下面的方法添加到placeOrder()

URLSession.shared.dataTask(with: request) { data, response, error in
    // handle the result here.
}.resume()

然后是最重要的部分:我们需要读取请求的结果。如果出现错误 —— 比如没有网络连接 —— 我们打印消息并且返回。

把下面这个方法添加到placeOrder()

guard let data = data else {
    print("No data in response: \(error?.localizedDescription ?? "Unknown error").")
    return
}

如果上面的 guard 语句通过,说明我们从服务器拿到了某条数据。因为我们用的是 ReqRes.in,所以实际上拿到正是我们发出去的同一条订单数据。因此,我们可以用JSONDecoder把 JSON 解码回对象。

为了确认一切工作正常,我们需要显示一个包含订单细节的 alert。这个订单应该同我们发出去的完全一致,否则说明我们的代码中有错误。

显示一个 alert 需要用到存储消息的属性,以及是否显示的标记,把下面两个属性添加到CheckoutView

@State private var confirmationMessage = ""
@State private var showingConfirmation = false

把下面这个 modifier 添加到CheckoutViewGeometryReader

.alert(isPresented: $showingConfirmation) {
    Alert(title: Text("Thank you!"), message: Text(confirmationMessage), dismissButton: .default(Text("OK")))
}

接下来完成网络代码:我们需要解码返回的数据,并用解码后的数据设置消息,然后把showingConfirmation设置为true,以便 alert 显示。如果解码失败 —— 比如因为某种原因服务器返回的东西并不是订单 —— 那我们可以打印一条错误消息。

把最后的代码添加到placeOrder(),放在dataTask(with:)的完成闭包那里:

if let decodedOrder = try? JSONDecoder().decode(Order.self, from: data) {
    self.confirmationMessage = "Your order for \(decodedOrder.quantity)x \(Order.types[decodedOrder.type].lowercased()) cupcakes is on its way!"
    self.showingConfirmation = true
} else {
    print("Invalid response from server")
}

完成了网络代码,整个应用实际上也完成了。尝试运行代码,你可以选择蛋糕,输入配送信息,然后点击 Place Order ,查看 alert 显示。

完工。


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~