引言:REST vs GraphQL——Swift新时代中的经典对决
曾几何时,在Objective-C和基于选择器调度的时代,REST与GraphQL之间的选择并不那么尖锐。REST是王者,JSON是其忠实的随从。但时代变了。Swift已成为一种拥有强类型系统的语言,async/await、Codable以及Apollo iOS等强大框架相继出现。如今,“REST还是GraphQL?”这个问题有了新的含义:“哪个能为我的Swift项目带来更多收益?”
在本文中,我们不会复述维基百科。我们将以一个教育平台的真实iOS应用为例(没错,就是那个PythonLib),看看每种方法在Swift代码中的实际表现。您将了解到何时REST仍然占据主导,以及何时GraphQL才是真正的救星。
核心要点:REST与GraphQL的主要区别
对于刚接触这个话题的人来说,先了解一些基本概念。
- REST — 一种架构风格,其中每个资源(用户、课程、课时)都有其自己的URL。您会获得服务器决定返回的固定数据结构——通常包含大量冗余数据。
- GraphQL — 一种查询语言。您自己决定需要哪些字段。一个入口点(
/graphql)和一个查询可以替代多个REST请求。
理论上,GraphQL看起来像一颗银弹。但在Swift项目中存在一些细微差别,我们将在实践中逐一分析。
示例1:获取用户资料及其课程
Swift中的REST方法(URLSession + Codable)
假设我们有一个经典的REST API。要显示用户资料和课程列表,需要两个请求:
// 模型struct UserProfile: Codable { let id: Int let name: String let email: String let avatarURL: String}
struct Course: Codable { let id: Int let title: String let progress: Double}
// 网络层func fetchUserProfile(userId: Int) async throws -> UserProfile { let url = URL(string: "https://api.pythonlib.ru/users/\\(userId)")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(UserProfile.self, from: data)}
func fetchCourses(userId: Int) async throws -> [Course] { let url = URL(string: "https://api.pythonlib.ru/users/\\(userId)/courses")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode([Course].self, from: data)}
// 使用let profile = try await fetchUserProfile(userId: 42)let courses = try await fetchCourses(userId: 42)一切简单且可预测。但有一个问题:即使我们只需要姓名和邮箱,也会获取到资料的所有字段。此外,还需要两次网络请求。在慢速连接下,这会造成性能问题。
Swift中的GraphQL方法(Apollo iOS)
现在,通过GraphQL实现同样的功能。首先,在.graphql文件中定义查询:
query GetUserWithCourses($userId: ID!) { user(id: $userId) { id name email courses { id title progress } }}Apollo会自动生成类型化的Swift代码。在Xcode中,它看起来像这样:
import Apollo
let apolloClient = ApolloClient(url: URL(string: "https://api.pythonlib.ru/graphql")!)
func fetchUserWithCourses(userId: Int) async throws -> GetUserWithCoursesQuery.Data { return try await withCheckedThrowingContinuation { continuation in apolloClient.fetch(query: GetUserWithCoursesQuery(userId: userId)) { result in switch result { case .success(let graphQLResult): if let data = graphQLResult.data { continuation.resume(returning: data) } else if let errors = graphQLResult.errors {