- 入门 GraphQL & Apollo
- 开始
- 为何使用GraphQL?
- 在iOS中通过Apollo使用GraphQL
- 与GraphQL交互
- 准备GraphQL服务器
- 输入初始会议数据
- 配置Xcode并设置Apollo iOS客户端
- 初始化ApolloClient
- 创建与会者并查询会议列表
- 编写第一个Mutation
- 查询所有会议
- 显示会议的详细信息
- 改变参加意愿后自动更新UI
- 接下来如何?
入门 GraphQL & Apollo
本文翻译自 https://www.raywenderlich.com/158433/getting-started-graphql-apollo-ios
原作者:Nikolas Burk
译者:@nixzhu
当你配合一个REST API工作时,因为某个端点没有给你所需要的数据用于app的某些视图,你是否有感到过沮丧呢?例如需要多个请求才能从服务器获取到正确的信息,或者你需要报bug通知后端开发者才能调整API?现在,你无需再担忧,GraphQL和Apollo将拯救你。
GraphQL是一种新的API设计范式,由Facebook在2015年开源,不过早在2012,他们就开始用于给自家的app提供支持了。它消除了当今REST API的许多低效之处。对比REST,GraphQL只需要暴露一个端点,API的消费者就能精确地指定他们所需要的数据。
在本教程中,你将构建一个iPhone app来帮助用户计划参与哪些iOS会议。你将设置自己的GraphQL服务器并在app中用Apollo iOS Client与之交互。Apollo是一个网络库,它能让我们与GraphQL API工作起来如沐春风。
此app将包含如下特性:
- 显示iOS会议列表
- 标记自己想参加或不想参加某个会议
- 查看参加某个会议的同伴
为了本教程的缘故,你要先通过Node Package Manager安装一些工具,请确保你已经安装有npm,且版本在4.5.0(或以上)。
开始
下载并打开用于本教程的starter project。它已包含有必要的UI组件,因此你可以专注于同API打交道,把数据正确地带到app中。
Storyboard看起来如下:

此app使用CocoaPods,所以在下载好第三方包后,你要打开ConferencePlanner.xcworkspace。Apollo的pod已经被包含在项目中,不过你应该确保安装的是最新的版本。
打开终端,导航到项目所在目录,并执行pod install来更新:

为何使用GraphQL?
REST API暴露多个端点,每个端点都返回特定的信息。例如,你可能有如下端点:
/conferences:返回一个所有会议的列表,每个会议都有id、name、city和year字段/conferences/__id__/attendees:返回某个会议所有的参加者(包括id和name)和会议id
想象一下,你所写的app要显示所有会议的列表,加上每个会议最近注册的三个参加者。你有何选择?

选项1:修改API
告诉后端开发者修改API,这样每次调用/conferences时都返回最近注册的三个参加者:

选项2:发送n+1次请求
发送n+1次请求(此处n是会议的数量)来获取所需的信息,并且接受你可能会耗尽用户的网络流量的后果,因为虽然每个会议你只需要最近的三个与会者却也必须下载全部。

这两个选项都不是很有吸引力,在更大的开发项目里也不具有很好的伸缩性!
使用GraphQL,你就可以简单地指定你在单个请求里所需要的数据,使用GraphQL语法以申明式的方式描述即可:
{allConferences {namecityyearattendees(last: 3) {name}}}
这个查询的应答将包含一个会议的列表,其中每个会议都有name、city和year,而且带有三个最新的与会者。
在iOS中通过Apollo使用GraphQL
GraphQL在移动开发者社区中还不是很流行,不过很可能会随着更多相关工具的改进而改变。这种势头表现在Apollo iOS客户端上,它实现了许多方便特性,让你与API合作时更轻松。
目前,它的主要特性如下:
- 基于数据要求的静态类型生成(译者注:即自动生成模型代码)
- 缓存与监听查询
通过本教程,你对这两个特性都会有所体会。
与GraphQL交互
与API交互的主要目标通常是:
- 获取数据
- 创建、更新和删除数据
在GraphQL中,获取数据通过query完成,而写入数据库通过mutation来达成。
一个mutation看起来很像一个query,同样允许你申明需要服务器返回的信息,这就确保了在一个单程往返中你就可以获取更新后的信息。
考虑如下两个简单例子:
query AllConferences {allConferences {idname}}
此query获取所有的会议并返回一个JSON数组,其中每个对象包带有会议的id和name。
mutation CreateConference {createConference(name: "WWDC", city: "San Jose", year: "2017") {id}}
此mutation创建了一个新会议并类似地返回其id。
如果你对这种语法还没什么感觉,别担心,稍后会探讨更多细节。
准备GraphQL服务器
为了本教程之目的,你将基于一个数据模型使用一个叫做Graphcool的服务来生成一个GraphQL服务器。
说到数据模型,下面是其样子,它的语法叫做GraphQL Interface Definition Language (IDL):
type Conference {id: String!name: String!city: String!year: String!attendees: [Attendee] @relation(name: "Attendees")}type Attendee {id: String!name: String!conferences: [Conference] @relation(name: "Attendees")}
GraphQL有它自己的类型系统,你可在其上构建自己的类型。如本例中的Conference和Attendee类型。每个类型都有一些属性,GraphQL术语叫做字段(field)。注意跟在每个类型后的!,它表示此字段是必须的。
话不多说,开始创建你的GraphQL服务器吧!
通过npm安装Graphcool CLI。打开终端,键入:
npm install -g graphcool
使用graphcool创建服务器:
graphcool init --schema http://graphqlbin.com/conferences.graphql --name ConferencePlanner
这个命令会创建一个叫做ConferencePlanner的Graphcool项目。在此之前,它还会打开一个浏览器窗口,你需要创建一个Graphcool账户(译者注:推荐使用GitHub登录)。一旦建立好,你就能访问GraphQL的全部功能了。

拷贝Simple API端点留作之后使用。
搞定!你现在能访问完整的GraphQL API并通过Graphcool console管理。
输入初始会议数据
在继续之前,先准备一些初始数据到数据库中。
将前一步中Simple API返回的端点拷贝到浏览器地址栏中。这将打开一个GraphQL Playground,你可在其中用交互式的方式探索API。
添加如下GraphQL代码到Playground左边的输入框,以创建一些初始数据:
mutation createUIKonfMutation {createConference(name: "UIKonf", city: "Berlin", year: "2017") {id}}mutation createWWDCMutation {createConference(name: "WWDC", city: "San Jose", year: "2017") {id}}
这个代码片段包含两个GraphQL mutation。点击Play按钮并通过下拉菜单将每个mutaiton都选择一次(译者注:每次执行一个mutation):

这就创建了两个新会议。为了验证已经创建成功,你可以通过Graphcool console中的data browser查看当前数据库的状态,或者发送之前见过的allConferences query。

配置Xcode并设置Apollo iOS客户端
之前提到,Apollo iOS客户端能做静态类型生成。也就是说,你不再需要写模型来表示来自你应用域名的信息了。作为替代,Apollo iOS客户端使用GraphQL query中的信息来生成对应的Swift类型。
注意:这种方式消除了Swift中JSON解析的不便。由于JSON没有类型,唯一真正安全的解析方式是在Swift类型上使用可选值,因为你不能100%确定JSON数据中包含某个特定类型的属性。
要在Xcode中实现静态类型生成,你要先配置几步:
1. 安装apollo-codegen
apollo-codegen会搜索Xcode项目中的GraphQL代码,并生成Swift类型。
打开终端,输入:
npm install -g apollo-codegen

2. 添加build phase
在Xcode中,选择Project Navigator中的ConferencePlanner。选择名为ConferencePlanner的target。选择Build Phases栏,再点击左上角的+按钮。
从菜单中选择New Run Script Phase:

重命名新建的build phase为Generate Apollo GraphQL API。并拖动它到Compile Sources上方。
拷贝如下代码片段到输入框(目前写着Type a script or drag a script file from your workspace to insert its path)中:
APOLLO_FRAMEWORK_PATH="$(eval find $FRAMEWORK_SEARCH_PATHS -name "Apollo.framework" -maxdepth 1)"if [ -z "$APOLLO_FRAMEWORK_PATH" ]; thenecho "error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project."exit 1ficd "${SRCROOT}/${TARGET_NAME}"$APOLLO_FRAMEWORK_PATH/check-and-run-apollo-codegen.sh generate $(find . -name '*.graphql') --schema schema.json --output API.swift
检查一下,你的Build Phases应该看起来如下:

3. 添加schema文件
在此你又会用到Simple API的端点。打开终端,输入如下命令(替换其中的__SIMPLE_API_ENDPOINT__为你之前生成的自定义GraphQL端点):
apollo-codegen download-schema __SIMPLE_API_ENDPOINT__ --output schema.json
注意:如果你丢失了GraphQL端点,你可以在Graphcool console中点击左下角的ENDPOINTS按钮找回。
接下来,将此文件放到Xcode工程的根目录。和AppDelegate.swift在同一个目录,即ConferencePlanner-starter/ConferencePlanner。

快速小结一下刚刚做的事:
- 首先安装
apollo-codegen,它用于生成Swift类型 - 接着,在Xcode中添加一个build phase,它会在编译之前让
apollo-codegen介入 - 之后到你实际的GraphQL guery(稍后添加),
apollo-codegen要求项目根目录里有一个上一步已下载的schema文件
初始化ApolloClient
终于要写点儿实际的代码了!
打开AppDelegate.swift,添加如下代码,注意替换其中的__SIMPLE_API_ENDPOINT__为你自己的端点。
import Apollolet graphQLEndpoint = "__SIMPLE_API_ENDPOINT__"let apollo = ApolloClient(url: URL(string: graphQLEndpoint)!)
传递了Simple API端点后,ApolloClient就知道该和哪个GraphQL服务器通信了。生成的apollo对象将是你与API打交道的主要界面。
创建与会者并查询会议列表
所有与GraphQL API交互的准备工作都已搞定!首先,确保使用此app的用户能通过取个名字来进行注册。
编写第一个Mutation
在Xcode中的GraphQL group中新建一个文件,记得用Other下的Empty模版,命名为RegisterViewController.graphql:

接下来,添加如下mutation到此文件中:
# 1mutation CreateAttendee($name: String!) {# 2createAttendee(name: $name) {# 3idname}}
稍微解释一下:
- 这里用了mutation签名(类似Swift的函数)。这个mutation叫做
CreateAttendee,接受一个名为name且类型为String的参数。感叹号表示这个参数必须传递。 createAttendee引用了GraphQL API所暴露的一个mutaion。Graphcool Simple API给每个类型都默认提供一个create-mutation。- 此mutation的payload,即你希望服务器执行此mutation后返回的数据。
在下一次构建项目时,apollo-codegen就会找到这些代码并为这个mutation生成Swift表示。用快捷键Cmd+B构建吧。
注意:如果你想给GraphQL代码增加语法高亮,请参考这里的指导。
当apollo-codegen初次运行,它会在项目根目录创建一个叫做API.swift的新文件。所有后续的生成只会更新此文件。
生成的API.swift文件位于项目根目录,但你还需要将其添加到Xcode中,拖动它到GraphQL group里。注意不要选中Copy items if needed!

观察API.swift文件的内容,你会看到一个叫做CreateAttendeeMutation的类。它的初始化方法接受一个name变量作为参数。它还有一个嵌套的结构体,叫做Data,它又嵌套另一个结构体,叫做CreateAttendee。这会作为mutation的返回数据,包含attendee的id和name。
接下来,你要使用此mutation。打开RegisterViewController.swift并实现createAttendee方法如下:
func createAttendee(name: String) {activityIndicator.startAnimating()// 1let createAttendeeMutation = CreateAttendeeMutation(name: name)// 2apollo.perform(mutation: createAttendeeMutation) { [weak self] result, error inself?.activityIndicator.stopAnimating()if let error = error {print(error.localizedDescription)return}// 3currentUserID = result?.data?.createAttendee?.idcurrentUserName = result?.data?.createAttendee?.nameself?.performSegue(withIdentifier: "ShowConferencesAnimated", sender: nil)}}
上面的代码:
- 使用用户提供的名字初始化mutation
- 使用
apollo实例发送此mutation到API - 接受从服务器返回的数据,并存在全局变量上,代表当前的用户
注意:本教程所有的API调用都遵循如下模式:首先生成一个query或mutation的实例,然后将其传递给ApolloClient,最后在回调中使用结果。
因为允许用户改名,你可添加第二个mutation。打开RegisterViewController.graphql并添加如下代码:
mutation UpdateAttendeeName($id: ID!, $newName: String!) {updateAttendee(id: $id, name: $newName) {idname}}
按下Cmd+B让apollo-codegen为此mutation生成Swift代码。之后,打开RegisterViewController.swift并替换updateAttendee如下:
func updateAttendee(id: String, newName: String) {activityIndicator.startAnimating()let updateAttendeeNameMutation = UpdateAttendeeNameMutation(id: id, newName: newName)apollo.perform(mutation: updateAttendeeNameMutation) { [weak self] result, error inself?.activityIndicator.stopAnimating()if let error = error {print(error.localizedDescription)return}currentUserID = result?.data?.updateAttendee?.idcurrentUserName = result?.data?.updateAttendee?.nameself?.performSegue(withIdentifier: "ShowConferencesAnimated", sender: nil)}}
这些代码和createAttendee几乎一样,除了要传递一个id来指代你要更新的是哪个用户。
编译并运行,输入一个名字,然后点击Save按钮。一个新的attentee就在GraphQL后端被创建好了。

你可在Playground中验证,发送allAttendees query即可:

查询所有会议
下一步要在ConferencesTableViewController中显示所有的会议。
在GraphQL group中新建一个文件,命名为ConferenceTableViewController.graphql并添加如下代码:
fragment ConferenceDetails on Conference {idnamecityyearattendees {id}}query AllConferences {allConferences {...ConferenceDetails}}
这里的fragment是什么东西?
简单来说,Fragments是可重用的零件,它能包装GraphQL类型的一些字段。它们与静态类型一起使用非常方便,因为它们增强了GraphQL服务器返回的信息的可重用性,并且每个片段将由其自己的结构体表示。
Fragment能通过...加上片段名集成到任何query或mutation中。当AllConferencesquery被发出时,...ConferenceDetails就被ConferenceDetails片段中包含的所有字段替换。(译者注:就像宏替换一样)
接下来该使用query填充table view了。
按下Cmd+B以确保生成新的query和fragment的Swift代码,然后打开ConferencesTableViewController.swift并添加如下属性到其顶部:
var conferences: [ConferenceDetails] = [] {didSet {tableView.reloadData()}}
在viewDidLoad最后,添加如下代码以发送query并显示结果:
let allConferencesQuery = AllConferencesQuery()apollo.fetch(query: allConferencesQuery) { [weak self] result, error inguard let conferences = result?.data?.allConferences else { return }self?.conferences = conferences.map { $0.fragments.conferenceDetails }}
和你在第一个mutation所看到的一样,你使用的是同样的模式,不过这次你发送的是一个query。在初始化此query之后,你将其传递给apollo实例并在回调中获取会议列表。这个列表的类型是[AllConferencesQuery.Data.AllConference],所以为了使用这个信息,你要访问每个元素的fragments并映射出ConferenceDetails。
剩下的就是告知UITableView如何显示会议数据了。
打开ConferenceCell.swift并添加如下属性:
var conference: ConferenceDetails! {didSet {nameLabel.text = "(conference.name) (conference.year)"let attendeeCount = conference.numberOfAttendeesinfoLabel.text ="\(conference.city) (\(attendeeCount) \(attendeeCount == 1 ? "attendee" : "attendees"))"}}
注意代码还不能通过编译,因为numberOfAttendees还不可用。你稍后会修复它。
接下来,打开ConferencesTableViewController.swift并替换UITableViewDataSource实现如下:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return conferences.count}override func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell = tableView.dequeueReusableCell(withIdentifier: "ConferenceCell") as! ConferenceCelllet conference = conferences[indexPath.row]cell.conference = conferencecell.isCurrentUserAttending = conference.isAttendedBy(currentUserID!)return cell}
这是标准的UITableViewDataSource实现。然而,编译器会抱怨不能在ConferenceDetails中找到isAttendedBy。
numberOfAttendees和isAttendedBy都表示了有用的信息,我们希望它们作为“模型”的工具函数。然而,记得吗,ConferenceDetails是自动生成在_API.swift里的。你不应该手动修改这个文件,因为你的改动会在Xcode下次构建时被覆盖!
逃离此困境的方法之一是在另外一个文件中创建一个extension来实现你想要的功能。打开Utils.swift并添加如下extension:
extension ConferenceDetails {var numberOfAttendees: Int {return attendees?.count ?? 0}func isAttendedBy(_ attendeeID: String) -> Bool {return attendees?.contains(where: { $0.id == attendeeID }) ?? false}}
运行app,你会看到之前添加的会议都出现在列表中。

显示会议的详细信息
ConferenceDetailViewController会显示被选中的会议的详细信息,包括与会者的列表。
你将通过编写GraphQL query并生成Swift类型来准备。
创建一个新文件ConferenceDetailViewController.graphql并添加如下代码:
query ConferenceDetails($id: ID!) {conference: Conference(id: $id) {...ConferenceDetails}}query AttendeesForConference($conferenceId: ID!) {conference: Conference(id: $conferenceId) {idattendees {...AttendeeDetails}}}fragment AttendeeDetails on Attendee {idname_conferencesMeta {count}}
在第一个query中,你提供一个id来请求特定的会议。第二个query返回此会议所有的与会者,对于每一个与会者,所有信息都由AttendeeDetails指定,包括id和name,以及与会者参加的所有会议的数量。
The _conferencesMeta field in AttendeeDetails fragment allows you to retrieve additional information about the relation. Here you’re asking for the number of attendees using count.
AttendeeDetailsfragment中的_conferencesMeta字段让你可以获取额外的关系信息。你通过使用count得到与会者所有会议的数量。(译者注:这里的原文是”Here you’re asking for the number of attendees using count“,疑似有误)
构建应用生成Swift类型。
接下来,打开ConferenceDetailViewController.swift并添加如下属性到IBOutlet声明的下方:
var conference: ConferenceDetails! {didSet {if isViewLoaded {updateUI()}}}var attendees: [AttendeeDetails]? {didSet {attendeesTableView.reloadData()}}var isCurrentUserAttending: Bool {return conference?.isAttendedBy(currentUserID!) ?? false}
头两个属性实现的didSet属性监听器确保UI的即时更新。最后一个计算当前用户参与的会议的意图是否被显示。
updateUI方法将用选择的会议信息来配置UI元素。实现如下:
func updateUI() {nameLabel.text = conference.nameinfoLabel.text = "(conference.city), (conference.year)"attendingLabel.text = isCurrentUserAttending ? attendingText : notAttendingTexttoggleAttendingButton.setTitle(isCurrentUserAttending ? attendingButtonText : notAttendingButtonText, for: .normal)}
最后,在ConferenceDetailViewcontroller.swift中替换tableView(_和
)tableView(_的实现如下:
)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return attendees?.count ?? 0}func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {guard let attendees = self.attendees else { return UITableViewCell() }let cell = tableView.dequeueReusableCell(withIdentifier: "AttendeeCell")!let attendeeDetails = attendees[indexPath.row]cell.textLabel?.text = attendeeDetails.namelet otherConferencesCount = attendeeDetails.numberOfConferencesAttending - 1cell.detailTextLabel?.text = "attends (otherConferencesCount) other conferences"return cell}
如之前所见,编译器会抱怨AttendeeDetails中没有numberOfConferencesAttending。你同样通过将其实现在extension中来修复。
打开Utils.swift添加如下extension:
extension AttendeeDetails {var numberOfConferencesAttending: Int {return conferencesMeta.count}}
最后通过在viewDidLoad中加载数据来完成ConferenceDetailViewController:
let conferenceDetailsQuery = ConferenceDetailsQuery(id: conference.id)apollo.fetch(query: conferenceDetailsQuery) { result, error inguard let conference = result?.data?.conference else { return }self.conference = conference.fragments.conferenceDetails}let attendeesForConferenceQuery = AttendeesForConferenceQuery(conferenceId: conference.id)apollo.fetch(query: attendeesForConferenceQuery) { result, error inguard let conference = result?.data?.conference else { return }self.attendees = conference.attendees?.map { $0.fragments.attendeeDetails }}
最后,你需要传递被选中会议的信息给ConferenceDetailViewController,这刚好可在segue被执行时搞定。
打开ConferencesTableViewController.swift并实现prepare(for如下:
)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {let conferenceDetailViewController = segue.destination as! ConferenceDetailViewControllerconferenceDetailViewController.conference = conferences[tableView.indexPathForSelectedRow!.row]}
好了!运行app并从列表中选择一个会议。在详细界面,你会看到被选中会议的细节信息。

改变参加意愿后自动更新UI
使用Apollo iOS客户端的一大优势是它对之前查询的规范化和缓存。当发送一个mutation时,它知道数据中的哪些比特改变了,然后特定地更新缓存,而不需要重新发送原始的query。一个很棒的副作用是它允许“自动UI更新”,接下来体会。
在ConferenceDetailViewController中,有个按钮允许用户修改他们参加会议的意愿。为了改变后端的状态,你需要先在ConferenceDetailViewController.graphql中创建两个mutation:
mutation AttendConference($conferenceId: ID!, $attendeeId: ID!) {addToAttendees(conferencesConferenceId: $conferenceId, attendeesAttendeeId: $attendeeId) {conferencesConference {idattendees {...AttendeeDetails}}}}mutation NotAttendConference($conferenceId: ID!, $attendeeId: ID!) {removeFromAttendees(conferencesConferenceId: $conferenceId, attendeesAttendeeId: $attendeeId) {conferencesConference {idattendees {...AttendeeDetails}}}}
第一个mutation用于添加一个与会者到会议;第二个移除一个与会者。
构建应用确保所有的mutation的Swift类型都被创建。
打开ConferenceDetailViewController.swift并替换attendingButtonPressed方法如下:
@IBAction func attendingButtonPressed() {if isCurrentUserAttending {let notAttendingConferenceMutation =NotAttendConferenceMutation(conferenceId: conference.id,attendeeId: currentUserID!)apollo.perform(mutation: notAttendingConferenceMutation, resultHandler: nil)} else {let attendingConferenceMutation =AttendConferenceMutation(conferenceId: conference.id,attendeeId: currentUserID!)apollo.perform(mutation: attendingConferenceMutation, resultHandler: nil)}}
如果你现在运行app,你将能改变参加的某个会议的状态(你可通过在Graphcool console的数据浏览器中验证)。但目前这个改变还不会反映到UI上。
别担心,Apollo iOS客户端会罩着你!通过使用GraphQLQueryWatcher,你能监听mutation带来的改变。要使用GraphQLQueryWatcher,只需做如下少量修改。
首先,打开ConferenceDetailViewController.swift并添加两个属性在顶部:
var conferenceWatcher: GraphQLQueryWatcher<ConferenceDetailsQuery>?var attendeesWatcher: GraphQLQueryWatcher<ConferenceDetailsQuery>?
接下来,你要修改viewDidLoad中发送query的方式,使用方法watch替换fetch,并将返回值赋给上面创建的属性:
...let conferenceDetailsQuery = ConferenceDetailsQuery(id: conference.id)conferenceWatcher = apollo.watch(query: conferenceDetailsQuery) { [weak self] result, error in guard let conference = result?.data?.conference else { return }self?.conference = conference.fragments.conferenceDetails}...
以及
...let attendeesForConferenceQuery = AttendeesForConferenceQuery(conferenceId: conference.id)attendeesWatcher = apollo.watch(query: attendeesForConferenceQuery) { [weak self] result, error inguard let conference = result?.data?.conference else { return }self?.attendees = conference.attendees?.map { $0.fragments.attendeeDetails }}...
与ConferenceDetailsQuery相关的数据或与AttendeesForConferenceQuery在缓存中的修改相关的数据,都会导致传递给watch的尾随闭包的的执行,从而更新UI。
最后一件为了让watchers正常工作的事是实现ApolloClient实例的cacheKeyForObject方法。这个方法告诉Apollo你想如何唯一识别它放入缓存的对象。在此例中,检查属性id就很好。
实现cacheKeyForObject的绝佳之处是在app初次启动时。打开AppDelegate.swift并添加如下代码到application(_的
)return语句前:
apollo.cacheKeyForObject = { $0["id"] }
注意:如果你想知道更多关于为何这是必要的以及Apollo缓存的工作方式,你可阅读 Apollo blog。
再次运行app,并修改你参加会议的意图,现在UI会立即更新。然而,当你回到ConferencesTableViewController,你还不会看到会议cell中状态的更新。

为了修复此问题,同样是用一个GraphQLQueryWatcher。打开ConferencesTableViewController.swift并增加如下属性到类的顶部:
var allConferencesWatcher: GraphQLQueryWatcher?
接着,更新viewDidLoad中的query:
...let allConferencesQuery = AllConferencesQuery()allConferencesWatcher = apollo.watch(query: allConferencesQuery) { result, error inguard let conferences = result?.data?.allConferences else { return }self.conferences = conferences.map { $0.fragments.conferenceDetails }}...
这就确保了传递给watch的尾随闭包在每次缓存中与AllConferencesQuery相关的改变发生时自动执行。
接下来如何?
看看此教程的final project,也许你需要对比一下。
如果你想学到更多关于GraphQL的知识,你可以开始阅读它良好的文档,或者订阅GraphQL weekly。
更多关于GraphQL社区的精彩内容可在Apollo和Graphcool的博客中找到。
作为一个挑战,你可实现添加新会议的功能。这个特性同样已包含在样本代码中。
我们希望你学习GraphQL时感到有趣!请让我们知道你对这种新的API范式的想法,加入下面的论坛讨论。
欢迎转载,但请一定注明出处! https://github.com/nixzhu/dev-blog
