• 入门 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?现在,你无需再担忧,GraphQLApollo将拯救你。

    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看起来如下:

    Application Main Storyboard

    此app使用CocoaPods,所以在下载好第三方包后,你要打开ConferencePlanner.xcworkspaceApollo的pod已经被包含在项目中,不过你应该确保安装的是最新的版本。

    打开终端,导航到项目所在目录,并执行pod install来更新:

    Pod Install Output

    为何使用GraphQL?

    REST API暴露多个端点,每个端点都返回特定的信息。例如,你可能有如下端点:

    • /conferences:返回一个所有会议的列表,每个会议都有idnamecityyear字段
    • /conferences/__id__/attendees:返回某个会议所有的参加者(包括idname)和会议id

    想象一下,你所写的app要显示所有会议的列表,加上每个会议最近注册的三个参加者。你有何选择?

    iOS Screen Application Running

    选项1:修改API

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

    Conferences REST API Endpoint Data

    选项2:发送n+1次请求

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

    Conference Attendee REST Endpoint Data

    这两个选项都不是很有吸引力,在更大的开发项目里也不具有很好的伸缩性!

    使用GraphQL,你就可以简单地指定你在单个请求里所需要的数据,使用GraphQL语法以申明式的方式描述即可:

    1. {
    2. allConferences {
    3. name
    4. city
    5. year
    6. attendees(last: 3) {
    7. name
    8. }
    9. }
    10. }

    这个查询的应答将包含一个会议的列表,其中每个会议都有namecityyear,而且带有三个最新的与会者。

    在iOS中通过Apollo使用GraphQL

    GraphQL在移动开发者社区中还不是很流行,不过很可能会随着更多相关工具的改进而改变。这种势头表现在Apollo iOS客户端上,它实现了许多方便特性,让你与API合作时更轻松。

    目前,它的主要特性如下:

    1. 基于数据要求的静态类型生成(译者注:即自动生成模型代码)
    2. 缓存与监听查询

    通过本教程,你对这两个特性都会有所体会。

    与GraphQL交互

    与API交互的主要目标通常是:

    • 获取数据
    • 创建、更新和删除数据

    在GraphQL中,获取数据通过query完成,而写入数据库通过mutation来达成。

    一个mutation看起来很像一个query,同样允许你申明需要服务器返回的信息,这就确保了在一个单程往返中你就可以获取更新后的信息。

    考虑如下两个简单例子:

    1. query AllConferences {
    2. allConferences {
    3. id
    4. name
    5. }
    6. }

    此query获取所有的会议并返回一个JSON数组,其中每个对象包带有会议的idname

    1. mutation CreateConference {
    2. createConference(name: "WWDC", city: "San Jose", year: "2017") {
    3. id
    4. }
    5. }

    此mutation创建了一个新会议并类似地返回其id

    如果你对这种语法还没什么感觉,别担心,稍后会探讨更多细节。

    准备GraphQL服务器

    为了本教程之目的,你将基于一个数据模型使用一个叫做Graphcool的服务来生成一个GraphQL服务器。

    说到数据模型,下面是其样子,它的语法叫做GraphQL Interface Definition Language (IDL):

    1. type Conference {
    2. id: String!
    3. name: String!
    4. city: String!
    5. year: String!
    6. attendees: [Attendee] @relation(name: "Attendees")
    7. }
    8. type Attendee {
    9. id: String!
    10. name: String!
    11. conferences: [Conference] @relation(name: "Attendees")
    12. }

    GraphQL有它自己的类型系统,你可在其上构建自己的类型。如本例中的ConferenceAttendee类型。每个类型都有一些属性,GraphQL术语叫做字段(field)。注意跟在每个类型后的!,它表示此字段是必须的。

    话不多说,开始创建你的GraphQL服务器吧!

    通过npm安装Graphcool CLI。打开终端,键入:

    1. npm install -g graphcool

    使用graphcool创建服务器:

    1. graphcool init --schema http://graphqlbin.com/conferences.graphql --name ConferencePlanner

    这个命令会创建一个叫做ConferencePlanner的Graphcool项目。在此之前,它还会打开一个浏览器窗口,你需要创建一个Graphcool账户(译者注:推荐使用GitHub登录)。一旦建立好,你就能访问GraphQL的全部功能了。

    GraphCool command output showing Simple API and Relay API

    拷贝Simple API端点留作之后使用。

    搞定!你现在能访问完整的GraphQL API并通过Graphcool console管理。

    输入初始会议数据

    在继续之前,先准备一些初始数据到数据库中。

    将前一步中Simple API返回的端点拷贝到浏览器地址栏中。这将打开一个GraphQL Playground,你可在其中用交互式的方式探索API。

    添加如下GraphQL代码到Playground左边的输入框,以创建一些初始数据:

    1. mutation createUIKonfMutation {
    2. createConference(name: "UIKonf", city: "Berlin", year: "2017") {
    3. id
    4. }
    5. }
    6. mutation createWWDCMutation {
    7. createConference(name: "WWDC", city: "San Jose", year: "2017") {
    8. id
    9. }
    10. }

    这个代码片段包含两个GraphQL mutation。点击Play按钮并通过下拉菜单将每个mutaiton都选择一次(译者注:每次执行一个mutation):

    GraphQL playground

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

    GraphQL Playground AllConferences query result

    配置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类型。

    打开终端,输入:

    1. npm install -g apollo-codegen

    NPM results from apollo-codegen installation

    2. 添加build phase

    在Xcode中,选择Project Navigator中的ConferencePlanner。选择名为ConferencePlanner的target。选择Build Phases栏,再点击左上角的+按钮。

    从菜单中选择New Run Script Phase

    Xcode altering build phases adding new run script

    重命名新建的build phase为Generate Apollo GraphQL API。并拖动它到Compile Sources上方。

    拷贝如下代码片段到输入框(目前写着Type a script or drag a script file from your workspace to insert its path)中:

    1. APOLLO_FRAMEWORK_PATH="$(eval find $FRAMEWORK_SEARCH_PATHS -name "Apollo.framework" -maxdepth 1)"
    2. if [ -z "$APOLLO_FRAMEWORK_PATH" ]; then
    3. echo "error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project."
    4. exit 1
    5. fi
    6. cd "${SRCROOT}/${TARGET_NAME}"
    7. $APOLLO_FRAMEWORK_PATH/check-and-run-apollo-codegen.sh generate $(find . -name '*.graphql') --schema schema.json --output API.swift

    检查一下,你的Build Phases应该看起来如下:

    Build Script Run Phase Code to Run

    3. 添加schema文件

    在此你又会用到Simple API的端点。打开终端,输入如下命令(替换其中的__SIMPLE_API_ENDPOINT__为你之前生成的自定义GraphQL端点):

    1. apollo-codegen download-schema __SIMPLE_API_ENDPOINT__ --output schema.json

    注意:如果你丢失了GraphQL端点,你可以在Graphcool console中点击左下角的ENDPOINTS按钮找回。
    GraphCool Endpoint Console

    接下来,将此文件放到Xcode工程的根目录。和AppDelegate.swift在同一个目录,即ConferencePlanner-starter/ConferencePlanner

    Finder File Listing showing Schema.json

    快速小结一下刚刚做的事:

    • 首先安装apollo-codegen,它用于生成Swift类型
    • 接着,在Xcode中添加一个build phase,它会在编译之前让apollo-codegen介入
    • 之后到你实际的GraphQL guery(稍后添加),apollo-codegen要求项目根目录里有一个上一步已下载的schema文件

    初始化ApolloClient

    终于要写点儿实际的代码了!

    打开AppDelegate.swift,添加如下代码,注意替换其中的__SIMPLE_API_ENDPOINT__为你自己的端点。

    1. import Apollo
    2. let graphQLEndpoint = "__SIMPLE_API_ENDPOINT__"
    3. let apollo = ApolloClient(url: URL(string: graphQLEndpoint)!)

    传递了Simple API端点后,ApolloClient就知道该和哪个GraphQL服务器通信了。生成的apollo对象将是你与API打交道的主要界面。

    创建与会者并查询会议列表

    所有与GraphQL API交互的准备工作都已搞定!首先,确保使用此app的用户能通过取个名字来进行注册。

    编写第一个Mutation

    在Xcode中的GraphQL group中新建一个文件,记得用Other下的Empty模版,命名为RegisterViewController.graphql

    Xcode New File Picker

    接下来,添加如下mutation到此文件中:

    1. # 1
    2. mutation CreateAttendee($name: String!) {
    3. # 2
    4. createAttendee(name: $name) {
    5. # 3
    6. id
    7. name
    8. }
    9. }

    稍微解释一下:

    1. 这里用了mutation签名(类似Swift的函数)。这个mutation叫做CreateAttendee,接受一个名为name且类型为String的参数。感叹号表示这个参数必须传递。
    2. createAttendee引用了GraphQL API所暴露的一个mutaion。Graphcool Simple API给每个类型都默认提供一个create-mutation。
    3. 此mutation的payload,即你希望服务器执行此mutation后返回的数据。

    在下一次构建项目时,apollo-codegen就会找到这些代码并为这个mutation生成Swift表示。用快捷键Cmd+B构建吧。

    注意:如果你想给GraphQL代码增加语法高亮,请参考这里的指导。

    apollo-codegen初次运行,它会在项目根目录创建一个叫做API.swift的新文件。所有后续的生成只会更新此文件。

    生成的API.swift文件位于项目根目录,但你还需要将其添加到Xcode中,拖动它到GraphQL group里。注意不要选中Copy items if needed

    Xcode Project Navigator showing API.swift in the GraphQL group

    观察API.swift文件的内容,你会看到一个叫做CreateAttendeeMutation的类。它的初始化方法接受一个name变量作为参数。它还有一个嵌套的结构体,叫做Data,它又嵌套另一个结构体,叫做CreateAttendee。这会作为mutation的返回数据,包含attendee的idname

    接下来,你要使用此mutation。打开RegisterViewController.swift并实现createAttendee方法如下:

    1. func createAttendee(name: String) {
    2. activityIndicator.startAnimating()
    3. // 1
    4. let createAttendeeMutation = CreateAttendeeMutation(name: name)
    5. // 2
    6. apollo.perform(mutation: createAttendeeMutation) { [weak self] result, error in
    7. self?.activityIndicator.stopAnimating()
    8. if let error = error {
    9. print(error.localizedDescription)
    10. return
    11. }
    12. // 3
    13. currentUserID = result?.data?.createAttendee?.id
    14. currentUserName = result?.data?.createAttendee?.name
    15. self?.performSegue(withIdentifier: "ShowConferencesAnimated", sender: nil)
    16. }
    17. }

    上面的代码:

    1. 使用用户提供的名字初始化mutation
    2. 使用apollo实例发送此mutation到API
    3. 接受从服务器返回的数据,并存在全局变量上,代表当前的用户

    注意:本教程所有的API调用都遵循如下模式:首先生成一个query或mutation的实例,然后将其传递给ApolloClient,最后在回调中使用结果。

    因为允许用户改名,你可添加第二个mutation。打开RegisterViewController.graphql并添加如下代码:

    1. mutation UpdateAttendeeName($id: ID!, $newName: String!) {
    2. updateAttendee(id: $id, name: $newName) {
    3. id
    4. name
    5. }
    6. }

    按下Cmd+Bapollo-codegen为此mutation生成Swift代码。之后,打开RegisterViewController.swift并替换updateAttendee如下:

    1. func updateAttendee(id: String, newName: String) {
    2. activityIndicator.startAnimating()
    3. let updateAttendeeNameMutation = UpdateAttendeeNameMutation(id: id, newName: newName)
    4. apollo.perform(mutation: updateAttendeeNameMutation) { [weak self] result, error in
    5. self?.activityIndicator.stopAnimating()
    6. if let error = error {
    7. print(error.localizedDescription)
    8. return
    9. }
    10. currentUserID = result?.data?.updateAttendee?.id
    11. currentUserName = result?.data?.updateAttendee?.name
    12. self?.performSegue(withIdentifier: "ShowConferencesAnimated", sender: nil)
    13. }
    14. }

    这些代码和createAttendee几乎一样,除了要传递一个id来指代你要更新的是哪个用户。

    编译并运行,输入一个名字,然后点击Save按钮。一个新的attentee就在GraphQL后端被创建好了。

    User Settings page for the application

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

    GraphCool Playground showing AllAttendees query

    查询所有会议

    下一步要在ConferencesTableViewController中显示所有的会议。

    在GraphQL group中新建一个文件,命名为ConferenceTableViewController.graphql并添加如下代码:

    1. fragment ConferenceDetails on Conference {
    2. id
    3. name
    4. city
    5. year
    6. attendees {
    7. id
    8. }
    9. }
    10. query AllConferences {
    11. allConferences {
    12. ...ConferenceDetails
    13. }
    14. }

    这里的fragment是什么东西?

    简单来说,Fragments是可重用的零件,它能包装GraphQL类型的一些字段。它们与静态类型一起使用非常方便,因为它们增强了GraphQL服务器返回的信息的可重用性,并且每个片段将由其自己的结构体表示。

    Fragment能通过...加上片段名集成到任何query或mutation中。当AllConferencesquery被发出时,...ConferenceDetails就被ConferenceDetails片段中包含的所有字段替换。(译者注:就像宏替换一样)

    接下来该使用query填充table view了。

    按下Cmd+B以确保生成新的query和fragment的Swift代码,然后打开ConferencesTableViewController.swift并添加如下属性到其顶部:

    1. var conferences: [ConferenceDetails] = [] {
    2. didSet {
    3. tableView.reloadData()
    4. }
    5. }

    viewDidLoad最后,添加如下代码以发送query并显示结果:

    1. let allConferencesQuery = AllConferencesQuery()
    2. apollo.fetch(query: allConferencesQuery) { [weak self] result, error in
    3. guard let conferences = result?.data?.allConferences else { return }
    4. self?.conferences = conferences.map { $0.fragments.conferenceDetails }
    5. }

    和你在第一个mutation所看到的一样,你使用的是同样的模式,不过这次你发送的是一个query。在初始化此query之后,你将其传递给apollo实例并在回调中获取会议列表。这个列表的类型是[AllConferencesQuery.Data.AllConference],所以为了使用这个信息,你要访问每个元素的fragments并映射出ConferenceDetails

    剩下的就是告知UITableView如何显示会议数据了。

    打开ConferenceCell.swift并添加如下属性:

    1. var conference: ConferenceDetails! {
    2. didSet {
    3. nameLabel.text = "(conference.name) (conference.year)"
    4. let attendeeCount = conference.numberOfAttendees
    5. infoLabel.text =
    6. "\(conference.city) (\(attendeeCount) \(attendeeCount == 1 ? "attendee" : "attendees"))"
    7. }
    8. }

    注意代码还不能通过编译,因为numberOfAttendees还不可用。你稍后会修复它。

    接下来,打开ConferencesTableViewController.swift并替换UITableViewDataSource实现如下:

    1. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    2. return conferences.count
    3. }
    4. override func tableView(_ tableView: UITableView,
    5. cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    6. let cell = tableView.dequeueReusableCell(withIdentifier: "ConferenceCell") as! ConferenceCell
    7. let conference = conferences[indexPath.row]
    8. cell.conference = conference
    9. cell.isCurrentUserAttending = conference.isAttendedBy(currentUserID!)
    10. return cell
    11. }

    这是标准的UITableViewDataSource实现。然而,编译器会抱怨不能在ConferenceDetails中找到isAttendedBy

    numberOfAttendeesisAttendedBy都表示了有用的信息,我们希望它们作为“模型”的工具函数。然而,记得吗,ConferenceDetails是自动生成在_API.swift里的。你不应该手动修改这个文件,因为你的改动会在Xcode下次构建时被覆盖!

    逃离此困境的方法之一是在另外一个文件中创建一个extension来实现你想要的功能。打开Utils.swift并添加如下extension:

    1. extension ConferenceDetails {
    2. var numberOfAttendees: Int {
    3. return attendees?.count ?? 0
    4. }
    5. func isAttendedBy(_ attendeeID: String) -> Bool {
    6. return attendees?.contains(where: { $0.id == attendeeID }) ?? false
    7. }
    8. }

    运行app,你会看到之前添加的会议都出现在列表中。

    Listing of the conferences

    显示会议的详细信息

    ConferenceDetailViewController会显示被选中的会议的详细信息,包括与会者的列表。

    你将通过编写GraphQL query并生成Swift类型来准备。

    创建一个新文件ConferenceDetailViewController.graphql并添加如下代码:

    1. query ConferenceDetails($id: ID!) {
    2. conference: Conference(id: $id) {
    3. ...ConferenceDetails
    4. }
    5. }
    6. query AttendeesForConference($conferenceId: ID!) {
    7. conference: Conference(id: $conferenceId) {
    8. id
    9. attendees {
    10. ...AttendeeDetails
    11. }
    12. }
    13. }
    14. fragment AttendeeDetails on Attendee {
    15. id
    16. name
    17. _conferencesMeta {
    18. count
    19. }
    20. }

    在第一个query中,你提供一个id来请求特定的会议。第二个query返回此会议所有的与会者,对于每一个与会者,所有信息都由AttendeeDetails指定,包括idname,以及与会者参加的所有会议的数量。

    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声明的下方:

    1. var conference: ConferenceDetails! {
    2. didSet {
    3. if isViewLoaded {
    4. updateUI()
    5. }
    6. }
    7. }
    8. var attendees: [AttendeeDetails]? {
    9. didSet {
    10. attendeesTableView.reloadData()
    11. }
    12. }
    13. var isCurrentUserAttending: Bool {
    14. return conference?.isAttendedBy(currentUserID!) ?? false
    15. }

    头两个属性实现的didSet属性监听器确保UI的即时更新。最后一个计算当前用户参与的会议的意图是否被显示。

    updateUI方法将用选择的会议信息来配置UI元素。实现如下:

    1. func updateUI() {
    2. nameLabel.text = conference.name
    3. infoLabel.text = "(conference.city), (conference.year)"
    4. attendingLabel.text = isCurrentUserAttending ? attendingText : notAttendingText
    5. toggleAttendingButton.setTitle(isCurrentUserAttending ? attendingButtonText : notAttendingButtonText, for: .normal)
    6. }

    最后,在ConferenceDetailViewcontroller.swift中替换tableView(_:numberOfRowsInSection:)tableView(_:cellForRowAt:)的实现如下:

    1. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    2. return attendees?.count ?? 0
    3. }
    4. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    5. guard let attendees = self.attendees else { return UITableViewCell() }
    6. let cell = tableView.dequeueReusableCell(withIdentifier: "AttendeeCell")!
    7. let attendeeDetails = attendees[indexPath.row]
    8. cell.textLabel?.text = attendeeDetails.name
    9. let otherConferencesCount = attendeeDetails.numberOfConferencesAttending - 1
    10. cell.detailTextLabel?.text = "attends (otherConferencesCount) other conferences"
    11. return cell
    12. }

    如之前所见,编译器会抱怨AttendeeDetails中没有numberOfConferencesAttending。你同样通过将其实现在extension中来修复。

    打开Utils.swift添加如下extension:

    1. extension AttendeeDetails {
    2. var numberOfConferencesAttending: Int {
    3. return conferencesMeta.count
    4. }
    5. }

    最后通过在viewDidLoad中加载数据来完成ConferenceDetailViewController

    1. let conferenceDetailsQuery = ConferenceDetailsQuery(id: conference.id)
    2. apollo.fetch(query: conferenceDetailsQuery) { result, error in
    3. guard let conference = result?.data?.conference else { return }
    4. self.conference = conference.fragments.conferenceDetails
    5. }
    6. let attendeesForConferenceQuery = AttendeesForConferenceQuery(conferenceId: conference.id)
    7. apollo.fetch(query: attendeesForConferenceQuery) { result, error in
    8. guard let conference = result?.data?.conference else { return }
    9. self.attendees = conference.attendees?.map { $0.fragments.attendeeDetails }
    10. }

    最后,你需要传递被选中会议的信息给ConferenceDetailViewController,这刚好可在segue被执行时搞定。

    打开ConferencesTableViewController.swift并实现prepare(for:sender:)如下:

    1. override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    2. let conferenceDetailViewController = segue.destination as! ConferenceDetailViewController
    3. conferenceDetailViewController.conference = conferences[tableView.indexPathForSelectedRow!.row]
    4. }

    好了!运行app并从列表中选择一个会议。在详细界面,你会看到被选中会议的细节信息。

    入门 GraphQL & Apollo - 图22

    改变参加意愿后自动更新UI

    使用Apollo iOS客户端的一大优势是它对之前查询的规范化和缓存。当发送一个mutation时,它知道数据中的哪些比特改变了,然后特定地更新缓存,而不需要重新发送原始的query。一个很棒的副作用是它允许“自动UI更新”,接下来体会。

    ConferenceDetailViewController中,有个按钮允许用户修改他们参加会议的意愿。为了改变后端的状态,你需要先在ConferenceDetailViewController.graphql中创建两个mutation:

    1. mutation AttendConference($conferenceId: ID!, $attendeeId: ID!) {
    2. addToAttendees(conferencesConferenceId: $conferenceId, attendeesAttendeeId: $attendeeId) {
    3. conferencesConference {
    4. id
    5. attendees {
    6. ...AttendeeDetails
    7. }
    8. }
    9. }
    10. }
    11. mutation NotAttendConference($conferenceId: ID!, $attendeeId: ID!) {
    12. removeFromAttendees(conferencesConferenceId: $conferenceId, attendeesAttendeeId: $attendeeId) {
    13. conferencesConference {
    14. id
    15. attendees {
    16. ...AttendeeDetails
    17. }
    18. }
    19. }
    20. }

    第一个mutation用于添加一个与会者到会议;第二个移除一个与会者。

    构建应用确保所有的mutation的Swift类型都被创建。

    打开ConferenceDetailViewController.swift并替换attendingButtonPressed方法如下:

    1. @IBAction func attendingButtonPressed() {
    2. if isCurrentUserAttending {
    3. let notAttendingConferenceMutation =
    4. NotAttendConferenceMutation(conferenceId: conference.id,
    5. attendeeId: currentUserID!)
    6. apollo.perform(mutation: notAttendingConferenceMutation, resultHandler: nil)
    7. } else {
    8. let attendingConferenceMutation =
    9. AttendConferenceMutation(conferenceId: conference.id,
    10. attendeeId: currentUserID!)
    11. apollo.perform(mutation: attendingConferenceMutation, resultHandler: nil)
    12. }
    13. }

    如果你现在运行app,你将能改变参加的某个会议的状态(你可通过在Graphcool console的数据浏览器中验证)。但目前这个改变还不会反映到UI上。

    别担心,Apollo iOS客户端会罩着你!通过使用GraphQLQueryWatcher,你能监听mutation带来的改变。要使用GraphQLQueryWatcher,只需做如下少量修改。

    首先,打开ConferenceDetailViewController.swift并添加两个属性在顶部:

    1. var conferenceWatcher: GraphQLQueryWatcher<ConferenceDetailsQuery>?
    2. var attendeesWatcher: GraphQLQueryWatcher<ConferenceDetailsQuery>?

    接下来,你要修改viewDidLoad中发送query的方式,使用方法watch替换fetch,并将返回值赋给上面创建的属性:

    1. ...
    2. let conferenceDetailsQuery = ConferenceDetailsQuery(id: conference.id)
    3. conferenceWatcher = apollo.watch(query: conferenceDetailsQuery) { [weak self] result, error in guard let conference = result?.data?.conference else { return }
    4. self?.conference = conference.fragments.conferenceDetails
    5. }
    6. ...

    以及

    1. ...
    2. let attendeesForConferenceQuery = AttendeesForConferenceQuery(conferenceId: conference.id)
    3. attendeesWatcher = apollo.watch(query: attendeesForConferenceQuery) { [weak self] result, error in
    4. guard let conference = result?.data?.conference else { return }
    5. self?.attendees = conference.attendees?.map { $0.fragments.attendeeDetails }
    6. }
    7. ...

    ConferenceDetailsQuery相关的数据或与AttendeesForConferenceQuery在缓存中的修改相关的数据,都会导致传递给watch的尾随闭包的的执行,从而更新UI。

    最后一件为了让watchers正常工作的事是实现ApolloClient实例的cacheKeyForObject方法。这个方法告诉Apollo你想如何唯一识别它放入缓存的对象。在此例中,检查属性id就很好。

    实现cacheKeyForObject的绝佳之处是在app初次启动时。打开AppDelegate.swift并添加如下代码到application(_:didFinishLaunchingWithOptions:)return语句前:

    1. apollo.cacheKeyForObject = { $0["id"] }

    注意:如果你想知道更多关于为何这是必要的以及Apollo缓存的工作方式,你可阅读 Apollo blog。

    再次运行app,并修改你参加会议的意图,现在UI会立即更新。然而,当你回到ConferencesTableViewController,你还不会看到会议cell中状态的更新。

    入门 GraphQL & Apollo - 图24

    为了修复此问题,同样是用一个GraphQLQueryWatcher。打开ConferencesTableViewController.swift并增加如下属性到类的顶部:

    1. var allConferencesWatcher: GraphQLQueryWatcher?

    接着,更新viewDidLoad中的query:

    1. ...
    2. let allConferencesQuery = AllConferencesQuery()
    3. allConferencesWatcher = apollo.watch(query: allConferencesQuery) { result, error in
    4. guard let conferences = result?.data?.allConferences else { return }
    5. self.conferences = conferences.map { $0.fragments.conferenceDetails }
    6. }
    7. ...

    这就确保了传递给watch的尾随闭包在每次缓存中与AllConferencesQuery相关的改变发生时自动执行。

    接下来如何?

    看看此教程的final project,也许你需要对比一下。

    如果你想学到更多关于GraphQL的知识,你可以开始阅读它良好的文档,或者订阅GraphQL weekly。

    更多关于GraphQL社区的精彩内容可在Apollo和Graphcool的博客中找到。

    作为一个挑战,你可实现添加新会议的功能。这个特性同样已包含在样本代码中。

    我们希望你学习GraphQL时感到有趣!请让我们知道你对这种新的API范式的想法,加入下面的论坛讨论。


    欢迎转载,但请一定注明出处! https://github.com/nixzhu/dev-blog