SwiftUI:使用 URLSession 发送和接收 Codable 数据

iOS为我们提供了从互联网发送和接收数据的内置工具,如果我们将其 Codable 持结合起来,那么就可以将 Swift 对象转换为 JSON 进行发送,然后将接收回来的 JSON 转换为 Swift 对象。更好的是,当请求完成时,我们可以立即将其数据分配给 SwiftUI 视图中的属性,从而导致用户界面更新。

为了演示这一点,我们可以从苹果的iTunes API中加载一些示例音乐JSON数据,并将其全部显示在一个SwiftUI List中。苹果的数据包含很多信息,但我们将把它缩减为两种类型:一个Result将存储一个曲目ID、名称和它所属的专辑,一个响应将存储一系列结果。

所以,从这段代码开始:

struct Response: Codable {
    var results: [Result]
}

struct Result: Codable {
    var trackId: Int
    var trackName: String
    var collectionName: String
}

我们现在可以编写一个简单的ContentView,它显示一个结果数组:

struct ContentView: View {
    @State private var results = [Result]()

    var body: some View {
        List(results, id: \.trackId) { item in
            VStack(alignment: .leading) {
                Text(item.trackName)
                    .font(.headline)
                Text(item.collectionName)
            }
        }
    }
}

一开始不会显示任何内容,因为 results 数组是空的。这就是我们的网络调用的来源:我们将要求iTunes API向我们发送Taylor Swift的所有歌曲的列表,然后使用JSONDecoder 将这些结果转换为一组 Result 实例。

为了更容易理解,让我们分几个阶段来写。首先,这里是基本方法——请将其添加到ContentView结构中:

func loadData() {

}

我们希望在显示列表时立即运行该操作,因此您应该将此修饰符添加到List中:

.onAppear(perform: loadData)

loadData()中,我们需要完成四个步骤:

  1. 创建我们要读取的URL。
  2. 将其包装在URLRequest中,这允许我们配置如何访问URL。
  3. 从该URL请求创建并启动网络任务。
  4. 处理网络任务的结果。

我们将从URL开始逐步添加这些内容。这需要有一个精确的格式:“itunes.apple.com”和一系列的参数——如果你在网上搜索“iTunes Search API”,你可以找到完整的参数集。在我们的例子中,我们将使用搜索词“Taylor Swift”和实体“song”,因此现在将其添加到loadData()中:

guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song") else {
    print("Invalid URL")
    return
}

接下来,我们需要将该URL包装成一个URLRequest。同样,在这里,我们将添加不同的自定义项来控制加载URL的方式,但在这里我们不需要任何东西,因此这只是一行代码——接下来将其添加到loadData()中:

let request = URLRequest(url: url)

第3步是使用我们刚才发出的URLRequest创建并启动一个网络任务。当你第一次看到它的时候,你会觉得这是一个相当奇怪的方法,它有一个特别常见的“陷阱”——一个你会犯一次又一次的错误,而且可能几年后还会犯。

我将首先向您展示代码,然后解释它的作用——将下方代码添加到loadData()

URLSession.shared.dataTask(with: request) { data, response, error in
    // step 4
}.resume()

URLSession是负责管理网络请求的iOS类。如果愿意,您可以创建自己的会话,但通常使用iOS创建的共享会话供我们使用,除非您需要某些特定行为,否则使用共享会话是可以的。

然后,我们的代码在该共享会话上调用dataTask(with:),这将从URLRequest创建一个网络任务,并在任务完成时运行一个闭包。在我们提供的代码中使用尾随闭包语法,可以看到它接受三个参数:

  • data 是从请求返回的任何数据。
  • response 是对数据的描述,其中可能包括数据的类型、发送了多少数据、是否有状态代码等等。
  • error 是发生的错误。

现在,巧妙地说,其中一些属性是互斥的,我的意思是,如果发生错误,那么就不会设置data,如果data被发送回去,那么就不会设置error。这种奇怪的状态之所以存在,是因为URLSession API是在Swift出现之前(OC时代)生成的,所以没有更好的方式来表示这种状态。

注意到我们直接对任务调用resume()的方式了吗?这就是问题所在——这是你会一次又一次忘记的事情。如果没有它,请求什么也做不了,你会盯着一个空白的屏幕。但有了它,请求立即开始,控制权移交给系统——它将自动在后台运行,即使在我们的方法结束后也不会被破坏。

当请求完成时,无论成功与否,都会进入第4步——这是数据任务中的闭包,负责处理数据或错误。在我们的例子中,我们将检查数据是否已设置,以及是否尝试将其解码为响应结构的实例,因为这是iTunes API返回的。实际上,我们并不需要整个Response,只需要其中的结果数组,这样我们的列表将显示它们。

不过,这里还有一个陷阱:URLSession自动在后台运行,这意味着它的完成闭包也将在后台运行。我所说的“后台”是指在技术上称为后台线程的代码,它是一段独立的代码,与程序的其他部分同时运行。这意味着网络请求可以运行,甚至需要几秒钟,而不会阻止我们的UI进行交互。

iOS需要在主线程上完成所有的用户界面相关工作,主线程是程序启动的地方。因为如果所有与UI相关的工作都发生在主线程上,这将停止两段试图同时操作用户界面的代码,那么它就不会发生冲突。

我们希望将视图的results属性更改为通过iTunes API下载的内容,然后更新我们的用户界面。这在后台线程上可能很有用,因为SwiftUI是超级智能的,但老实说,这不值得冒险——最好是在后台获取数据,在后台从JSON解码,然后在主线程上更新相关属性,以避免任何潜在的问题。

iOS为我们提供了一种向主线程发送工作的非常特殊的方式:DispatchQueue.main.async()。这需要一个要执行的工作闭包,并将其发送到主线程执行。从它的名字可以看出,实际发生的是它被添加到一个队列中——一个等待执行的大工作队列。“async”部分是“asynchronous”的缩写,这意味着我们自己的后台工作不会等待闭包运行;我们只是将它添加到队列中,然后在后台继续工作。

所以,将最后一个代码放在 //step 4注释的位置:

if let data = data {
    if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
        // 我们得到了有用的数据 - 返回到主线程
        DispatchQueue.main.async {
            // 更新我们的UI
            self.results = decodedResponse.results
        }

        // 所有工作都完成了,所以可以退出了
        return
    }
}

// 如果代码跑到这里了,说明发生了某些错误
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")

最后一行print()使用可选的链接和空合运算符,以确保在存在错误时打印错误,否则给出一个一般错误。

如果你现在运行代码,你应该会看到一个泰勒·斯威夫特歌曲的列表在短暂的停顿之后出现——考虑到最终结果的效果,这真的不是很多代码。

稍后在这个项目中,我们将研究如何自定义URLRequest,以便您可以发送可编码的数据,但现在这已经足够了——请将ContentView.swift重置为其原始状态,以便我们可以开始工作。

译自 Sending and receiving Codable data with URLSession and SwiftUI

SwiftUI:为 @Published 属性添加 Codable 支持 Hacking with iOS: SwiftUI Edition SwiftUI:验证和禁用表单

赏我一个赞吧~~~

你可能感兴趣的:(SwiftUI:使用 URLSession 发送和接收 Codable 数据)