12. 定义额外的突变
在本节中,您将实现额外的突变用于预订和取消行程。
添加BookTrip
突变
在沙盒,点击图标打开模式标签页,选择mutations
,查看bookTrips
突变
点击右侧的播放按钮,在探索器中打开此突变。点击加号按钮添加bookTrips
突变
您可以在左侧侧边栏中看到,该操作包含一个数组ID参数(已添加为$bookTripsLaunchIds
),操作返回的对象有三个属性:
- 一个表示预订是否成功的
success
布尔值 - 一个用于向用户显示的
message
字符串 - 当前用户已预订的
launches
列表
点击success
和message
旁边的加号,将它们添加到您的操作中。
在沙盒探索器的“变量”部分,添加一个标识符数组。在本例中,我们将使用单个标识符预订一次行程:
{"bookTripsLaunchIds": ["25"]}
接下来,在单词 "Variables" 的旁边,您会看到单词 "Headers"。点击它以打开头部信息部分。点击 "New Header" 按钮,然后在头部键文本框中输入 "Authorization",并将上一部分得到的令牌粘贴到值中:
现在,单击 提交 按钮,运行您的授权 查询。您将得到有关刚刚预订的行程(在本例中,为行程)的信息。
注意:如果您收到错误消息 "无法读取null的属性 'id'",这意味着根据您传递的令牌找不到用户。请确保您的授权头部格式正确且您已实际登录!
通过这样编写的 mutation,您可以同时预订您想要预订的任何数量的行程。然而,我们的应用程序的预订机制将只允许您一次预订一个行程。
幸运的是,有一种简单的方法可以更新 mutation 以仅需要一个单个对象。首先,将资源管理器中的 operation 名称更新为单数 BookTrip
。接下来,将 mutation 更新为接收单个 $id
,然后传递一个包含该 $id
的数组到 bookTrips
mutation:
mutation BookTrip($id:ID!) {bookTrips(launchIds:[$id]) {successmessage}}
这很有帮助,因为Swift代码生成现在将生成一个仅接受单个ID
的方法,而不是数组,但您仍然会在底层调用相同的mutation,而无需后端做出任何更改。
在Sandbox Explorer的Variables部分,更新JSON字典以使用id
作为键,并从标识符周围删除数组括号:
{"id": "25"}
点击提交操作按钮以运行您更新的查询。返回的响应应与您之前收到的响应相同:
现在您已经详细说明了操作,是时候将其放入应用程序中。转到文件 > 新建 > 文件... > 空白,将此文件命名为BookTrip.graphql
,并将其添加到您其他GraphQL文件旁边。将Sandbox Explorer中的最终查询粘贴进去。
mutation BookTrip($id: ID!) {bookTrips(launchIds: [$id]) {successmessage}}
现在在终端中运行代码生成以生成新的mutation代码。
实现bookTrip
逻辑
现在您拥有了BookTrip
mutation,是时候实现应用程序中预订旅行的逻辑了。首先,转到DetailViewModel.swift
并找到bookTrip()
方法。
用以下代码替换现有的函数
private func bookTrip(with id: RocketReserverAPI.ID) {Network.shared.apollo.perform(mutation: BookTripMutation(id: id)) { [weak self] result inguard let self = self else {return}switch result {case .success(let graphQLResult):if let bookingResult = graphQLResult.data?.bookTrips {if bookingResult.success {self.appAlert = .basic(title: "Success!",message: bookingResult.message ?? "Trip booked successfully")self.loadLaunchDetails()} else {self.appAlert = .basic(title: "Could not book trip",message: bookingResult.message ?? "Unknown failure")}}if let errors = graphQLResult.errors {self.appAlert = .errors(errors: errors)}case .failure(let error):self.appAlert = .errors(errors: [error])}}}
现在您已经有了预订旅行的代码。在您运行它之前,让我们添加取消行程的代码。
添加CancelTrip
mutation
取消行程CancelTrip
的流程与BookTrip
类似。返回到沙盒的“模式”标签页,选择突变,然后查看cancelTrip
突变的文档:
点击右侧的播放按钮在探索器中打开此操作,为新的操作在探索器中添加新标签页,然后点击加号创建您的操作。
再次勾选success
和message
,将它们添加到您想与取消信息一起获取的属性列表中。
同样,探索器在这里有点冗长,所以更新您的操作的名称和一个变量,使其更短:
mutation CancelTrip($id: ID!) {cancelTrip(launchId: $id) {successmessage}}
与bookTrips
的一个重要区别是,您只能一次取消一个行程,因为只能接受一个ID
作为参数。
在沙盒探索器的“变量”部分中,您可以使用与BookTrip
相同的JSON(因为它也使用了一个名为“id”的单个标识符):
{"id": "25"}
确保在“头部”部分中,您再次添加您的授权令牌(添加到BookTrip
的标签页上的令牌将不会传递到这个新标签页):
点击“提交操作”按钮以取消行程,您应该看到一个成功的请求:
再次回到Xcode,创建一个新空文件,命名为CancelTrip.graphql
,并将其添加到您其他GraphQL文件旁边。然后,粘贴从沙盒探索器中的最终查询:
mutation CancelTrip($launchId: ID!) {cancelTrip(launchId: $launchId) {successmessage}}
现在在终端中运行代码生成以生成新的mutation代码。
实现cancelTrip
逻辑
现在让我们来实现应用中取消行程的逻辑,回到DetailViewModel.swift
,找到cancelTrip()
方法,并用以下代码替换它:
private func cancelTrip(with id: RocketReserverAPI.ID) {Network.shared.apollo.perform(mutation: CancelTripMutation(launchId: id)) { [weak self] result inguard let self = self else {return}switch result {case .success(let graphQLResult):if let cancelResult = graphQLResult.data?.cancelTrip {if cancelResult.success {self.appAlert = .basic(title: "Trip cancelled",message: cancelResult.message ?? "Your trip has been officially cancelled")self.loadLaunchDetails()} else {self.appAlert = .basic(title: "Could not cancel trip",message: cancelResult.message ?? "Unknown failure.")}}if let errors = graphQLResult.errors {self.appAlert = .errors(errors: errors)}case .failure(let error):self.appAlert = .errors(errors: [error])}}}
我们还需要做的一件事是更新bookOrCancel()
方法,以便真正调用我们的bookTrip(...)
和cancelTrip(...)
方法,将TODO
替换为以下代码:
guard let launch = launch else {return}launch.isBooked ? cancelTrip(with: launch.id) : bookTrip(with: launch.id)
现在构建并运行应用程序,如果您转到任何启动的详细视图并点击“预定行程”,应该会看到一个消息表示行程已成功预定,但您会发现,即使您退出详细视图并再次进入,UI也不会更新。
为什么会这样?因为您在本地缓存的行程仍然有旧的isBooked
值。
有几种方法可以改变这一点,但到目前为止我们只关注一个需要您最少代码更改的方法:从网络重新查询预定信息。
从网络中强制获取数据
ApolloClient 的fetch
方法为大多数参数提供了默认值,因此如果您使用默认配置,您需要提供的唯一值就是Query
。
但是,需要注意的是一个重要的参数是cachePolicy
。默认情况下,它的值是returnCacheDataElseFetch
,它基本上是按照标签上的描述执行的:它在当前缓存(默认为内存缓存)中查找数据,如果不存在,则从网络中获取。
如果数据是存在的,默认的行为是返回本地副本以防止不必要的网络获取。然而,这种情况有时并不是我们想要的(尤其是在执行mutation之后)。
有几种不同的缓存策略可供选择,但要绝对强制从网络刷新并更新缓存,最简单的方法是使用fetchIgnoringCacheData
。这种策略在访问网络时绕过缓存,但也会将获取的结果存储在缓存中以供将来使用。
首先,我们需要将以下import
添加到DetailViewModel
中:
import Apollo
更新loadLaunchDetails
方法以接受一个参数来确定是否应该强制重新加载。如果应该强制重新加载,则将缓存策略从默认的.returnCacheDataElseFetch
(存在时会从缓存返回数据)更新为.fetchIgnoringCacheData
:
func loadLaunchDetails(forceReload: Bool = false) {guard forceReload || launchID != launch?.id else {return}let cachePolicy: CachePolicy = forceReload ? .fetchIgnoringCacheData : .returnCacheDataElseFetchNetwork.shared.apollo.fetch(query: LaunchDetailsQuery(launchId: launchID), cachePolicy: cachePolicy) { [weak self] result in// Network handling remains the same}}
接下来,更新bookTrip(...)
和cancelTrip(...)
方法以使用更新的loadLaunchDetails(...)
调用:
// bookTrip()self.appAlert = .basic(title: "Success!",message: bookingResult.message ?? "Trip booked successfully")self.loadLaunchDetails(forceReload: true)// cancelTrip()self.appAlert = .basic(title: "Trip cancelled",message: cancelResult.message ?? "Your trip has been officially cancelled")self.loadLaunchDetails(forceReload: true)
测试mutations
运行应用程序。当您预订或取消行程时,应用程序将获取更新的状态并使用正确的状态更新UI。当您出去再回来时,缓存将更新为最新状态,并显示最新状态。
在下一节中,您将学习如何使用订阅与Apollo客户端一起使用。