首先尝试GraphQL和Apollo

最近,我看到一条消息说,GitHub将其API从REST迁移到了GraphQL。 这让我真的对GraphQL及其提供的功能感到好奇。

介绍

GraphQL是一种新的API设计范例,于2015年由Facebook开源,但自2012年以来一直为他们的移动应用提供动力。它消除了当今REST API的许多低效率问题。 与REST相比,GraphQL API仅公开一个端点,并且API的使用者可以精确地指定所需的数据。 在iOS开发中,我们可以利用Apollo iOS客户端的优势对GraphQL服务器执行查询和变异,并以查询特定的Swift类型返回结果。 这意味着您不必处理解析JSON或传递字典并使客户端将值手动转换为正确类型的麻烦。 您也不必自己编写模型类型,因为它们是从UI使用的GraphQL定义生成的。

为什么选择GraphQL?

REST API公开了多个端点,每个端点都返回特定的信息。 例如,我有以下两个端点:

  1. / classes:返回类列表。
  2. / classes / id / students:返回参加此课程的学生列表。

现在,如果我想显示所有班级的列表以及两名参加该班级的学生。 有两种选择:

  1. 修改现有的API,使响应在每个班级中包含两名学生的信息。
  2. 多次致电/ classes / id / students以获取我想要的信息。

它们都不是一个好的解决方案,因为将来很难扩展。 但是,我可以在GraphQL的单个请求中简单地指定数据要求。 响应将包含一系列班级以及两名学生。

先决条件

在本文中,我想实现一个非常简单的应用程序,该应用程序显示班级列表以及班级中的老师和学生。 但是,在我们开始之前,仍有几件事要做。

首先,有必要配备GraphQL服务器,而Graphcool是一个完美的选择。 访问该网站并按照说明创建一个名为Schedule的项目。 此外,将以下代码添加到Graphcool控制台中以创建必要的架构。

type Class implements Node { 
id: ID! @isUnique
title: String!
createdAt: DateTime!
updatedAt: DateTime!
teacher: Teacher @relation(name: "Teacher")
students: [Student!]! @relation(name: "Students")
}
 type Teacher implements Node { 
id: ID! @isUnique
name: String!
class: Class @relation(name: "Teacher")
createdAt: DateTime!
updatedAt: DateTime!
}
 type Student implements Node { 
id: ID! @isUnique
name: String!
class: Class @relation(name: "Students")
createdAt: DateTime!
updatedAt: DateTime!
}

请记住保存简单API,以供以后使用。

由于我只允许我的应用程序查询服务器中的数据,因此在实现应用程序之前,我需要一些初始数据。 将Simple API复制并粘贴到任何浏览器的地址栏中,这将打开GraphQL Playground,它使我可以创建初始数据。 编写以下三个功能,然后使用“播放”按钮创建班级,老师和学生。

 mutation createClass { 
createClass(title: "Class_Title") {
id
}
}
 mutation createStudent { 
createStudent(name: "Student_Name") {
id
}
}
 mutation createTeacher { 
createTeacher(name: "Teacher_Name") {
id
}
}

然后,再次在Playground内编写以下函数,以将老师和学生添加到班级中。

 mutation attendClass($classID: ID!, $studentID: ID!) { 
addToStudents(classClassId: $classID, studentsStudentId: $studentID) {
classClass {
id
students {
id
}
}
}
}
 mutation teachClass($classID: ID!, $teacherID: ID!) { 
setTeacher(classClassId: $classID, teacherTeacherId: $teacherID) {
classClass {
id
teacher {
id
}
}
}
}

接下来要做的是配置Xcode并设置Apollo iOS客户端。 如前所述,Apollo iOS客户端具有静态类型生成功能。 这意味着我不必编写模型类型。 相反,Apollo iOS客户端使用我的GraphQL查询中的信息来生成Swift类型。 但是,我首先必须经历一些配置步骤。 创建一个名为Schedule的新Xcode项目,并通过Carthage安装Apollo iOS客户端。 之后,通过在终端中键入npm install -g apollo-codegennpm install -g apollo-codegen 。 然后,在Schedule目标中创建一个New Run Script Phase,并将以下代码片段复制到该字段中。

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

此外,请记住将构建阶段拖放到“编译源”上方。

最后一步是通过在schema.json键入apollo-codegen download-schema My_Simple_API --output schema.json来生成schema.json文件,并将schema.json文件移动到AppDelegate.swift所在的目录中。

实作

经过漫长的设置过程,让我们开始编写一些代码。 首先,在AppDelegate.swift文件中实例化ApolloClient

 let graphQLEndpoint = "My_Simple_API" 
let apollo = ApolloClient(url: URL(string: graphQLEndpoint)!)

此外,在项目中创建一个空文件,将其命名为ClassList.graphql ,然后将以下代码添加到文件中,以定义对类列表的查询。

 fragment TeacherDetails on Teacher { 
id
name
}
 fragment ClassDetails on Class { 
id
title
teacher {
...TeacherDetails
}
_studentsMeta {
count
}
}
 query AllClasses { 
allClasses {
...ClassDetails
}
}

然后,构建项目, apollo-codegen将找到此代码并生成Swift表示形式。 第一次运行apollo-codegen ,它将在名为API.swift的项目的根目录中创建一个新文件。 所有后续调用将仅更新现有文件。 生成的API.swift文件位于项目的根目录中,但是仍需要将其添加到项目中。 将其拖放到项目中,并确保取消选中“根据需要复制项目”复选框。

API.swift文件包含AllClassesQuery类及其对应的结构。 现在,我可以通过以下方式利用ApolloClient来获取类列表。

 let allClassesQuery = AllClassesQuery() 
apollo.fetch(query: allClassesQuery) { (result, error) in
guard let classes = result?.data?.allClasses else { return }
  let classDetails = classes.map { $0.fragments.classDetails } 
// ...
}

下一步是创建ClassDetails.graphql文件,以获取每个班级的老师和学生。 将以下代码添加到ClassDetails.graphql文件中。

 fragment StudentDetails on Student { 
id
name
}
 fragment ClassDetailsWithStudents on Class { 
id
title
teacher {
...TeacherDetails
}
students {
...StudentDetails
}
}
 query ClassDetails($classID: ID!) { 
class: Class(id: $classID) {
...ClassDetailsWithStudents
}
}

构建项目后,将生成ClassDetailsQuery类及其对应的结构,我可以采用类似的方法来获取教师和学生的信息。

 let classDetailsQuery = ClassDetailsQuery(classId: classID) 
apollo.fetch(query: classDetailsQuery) { (result, error) in
guard let classDetailsWithStudents = result?.data?.class?.fragments.classDetailsWithStudents else { return }
  // ... 
}

结论

这是整个示例项目。

尽管设置过程似乎很长,但有关后端服务器配置的工作却很多。 因此,对于iOS开发人员,我们应该集中精力于如何编写相应的.graphql文件以及如何利用Apollo iOS客户端。 更重要的是,Apollo iOS客户端提供了watch功能,并且只要本地缓存中的任何数据发生更改,它都会得到通知。 因此,可以用来更新UI。 此外,在Apollo和Graphcool博客上可以找到许多很棒的内容和功能。 欢迎任何评论和反馈,请分享您的想法。 谢谢!