加入我们,从10月8日至10日在纽约市,学习有关GraphQL联邦和API平台工程的最新技巧、趋势和新闻。加入我们参加纽约市的GraphQL Summit 2024
文档
免费开始

Apollo iOS 1.0 迁移指南

从 0.x 迁移到 1.0


1.0 提供了一个稳定的 API,使用现代 Swift 语言约定和功能。

在众多改进中,Apollo iOS 1.0 新增了以下功能:

  • 使用纯 Swift 代码编写的代码生成工具。
  • 生成的模型提高了可读性和功能性,同时减少了生成的代码大小。
  • 支持在多模块项目中使用生成的模型。
  • 支持通过类型安全的 API 解析缓存键。

本文介绍了 Apollo iOS 这个主要版本的显著变化,以及从 Apollo iOS 0.x 迁移到 1.0 的步骤。

迁移到 Apollo iOS 1.0 的步骤

  1. 首先了解关键更改
  2. 接下来,遵循我们的 分步指南 升级您的应用程序。
  3. 最后,查阅 破坏性更改 解决任何剩余的构建错误。

关键更改

生成的模式模块

0.x 版本的 Apollo iOS 为您的 操作定义、以及您模式中引用的输入对象和枚举生成模型。

Apollo iOS 1.0 在此基础上扩展,生成一个包含您的 及其类型定义的整个模块。

除了您的输入对象和枚举类型外,此模块还包含

  • 您的模式中对象、接口和联合的类型。
  • 可编辑的自定义定义。
  • 可编辑SchemaConfiguration.swift文件。
  • Apollo GraphQL执行器的元数据。

模式类型包含在其自己的命名空间中,以防止命名冲突。您可以生成此命名空间作为单独的模块,由您的项目导入,或作为您可以嵌入到应用程序目标中的非区分大小写的命名空间枚举。

多模块支持

Apollo iOS 1.0可以支持由多个模块组成的复杂应用程序单体应用程序目标。

生成的模式模块支持多模块项目,因为它包含一个模式的所有共享类型和元数据。这使得您可以将生成的操作模型移动到项目结构中的任何位置(只要它们与模式模块相链接)。

Apollo iOS的代码生成引擎提供了灵活的配置选项,使得代码生成与任何项目结构无缝工作。

逐步说明

在迁移到Apollo iOS 1.0之前,您应考虑项目结构并决定如何包含您的生成的模式模块和操作模型。

要了解如何将Apollo iOS最佳集成到满足您的项目需求,请参阅我们的项目配置文档

要迁移到Apollo iOS 1.0,您需要执行以下操作:

  1. 更新Apollo iOS依赖项
  2. 设置代码生成
  3. 替换代码生成构建阶段
  4. 重构您的代码以使用新API

此迁移过程的大部分涉及新的代码生成机制。

在这个过程中,我们将解释如何逐步移除旧0.x版本的过时部分。以下每个步骤也包含了关于任何破坏性API更改的解释。

步骤 1:升级到Apollo iOS 1.0

首先,将您的Apollo iOS依赖项更新到最新版本。您可以使用Swift Package Manager (SPM)或Cocoapods将Apollo iOS作为一个包包含在内。

为了接收错误修复和新功能,我们建议包含从1.0到下一个主要版本。

要查看Apollo iOS SDK提供的模块(并确定您需要哪些模块),请参阅SDK组件

Package.swift
.package(
url: "https://github.com/apollographql/apollo-ios.git",
.upToNextMajor(from: "1.0.0")
),
Podfile
pod 'Apollo' ~> '1.0'

注意:您可以将 Apollo iOS 1.0 构建为动态 .xcframework 或静态库。您还可以使用 Carthage 或 Buck 等构建工具预编译并包含 Apollo iOS 1.0 的 二进制文件(尽管目前我们没有 如何做这件事)。

第二步:设置代码生成

Apollo iOS 1.0 包含了一个新的代码生成引擎,该引擎是用纯 Swift 编码的,取代了旧的 apollo-tooling 库。要使用 1.0,您必须安装新的代码生成引擎并移除旧的引擎。

我们建议使用 Apollo Codegen CLI 运行新的代码生成引擎。您还可以在 Swift 脚本中运行代码生成以进行更高级的使用。

代码生成 CLI 设置

有关 CLI 设置说明,请选择您使用的方法来包含 Apollo

Swift脚本设置

如果您通过Swift脚本运行代码生成,请更新您的脚本以使用与您的Apollo版本匹配的ApolloCodgenLib版本。

然后,在您的脚本中使用新的配置值更新ApolloCodegenConfiguration。有关配置选项的列表,请参阅Codegen配置

步骤3:替换代码生成构建阶段

我们不再建议将Apollo的代码生成作为Xcode构建阶段运行。

您生成的文件在您修改.graphql操作定义(这种情况很少发生)时会发生更改。在每次构建上运行代码生成会增加构建时间并减缓开发速度。

相反,我们建议在您修改.graphql文件时手动(使用CLI)运行代码生成。

如果您希望在每次构建时继续运行代码生成,您可以更新您的构建脚本来运行CLI的generate命令。

步骤4:重构您的代码

在设计Apollo iOS 1.0的过程中,我们试图减少从旧版本迁移所需的代码更改数量。

以下是Apollo iOS 1.0带来的每个破坏性更改的解释以及如何在迁移过程中处理这些更改的提示。

破坏性更改

自定义标量

在Apollo iOS的0.x版本中,您模式的自定义标量默认情况下作为String类型暴露。如果您使用了--passthroughCustomScalars选项,则您生成的模型包括自定义的名称。您负责定义传递给自定义标量的类型。

在Apollo iOS 1.0中,操作模型使用自定义标量定义,默认情况下,Apollo iOS为所有引用的自定义标量生成typealias定义。这些定义位于您的模式模块中。所有自定义标量的默认实现是一个typealiasString

自定义标量文件一次生成。这意味着您可以编辑它们,并且后续的代码生成执行不会覆盖您的更改。

要将自定义标量类型迁移到Apollo iOS 1.0,请按照以下步骤操作:

有关定义自定义标量的更多详细信息,请参阅自定义标量

示例

我们定义一个scalar Coordinate,在GraphQL操作中进行引用。Apollo iOS生成Coordinate自定义标量

MySchema/CustomScalars/UUID.swift
public extension MySchema {
typealias Coordinate = String
}

具有名称Coordinate的自定义标量可以替换typealias,如下所示:

MySchema/CustomScalars/UUID.swift
public extension MySchema {
struct Coordinate: CustomScalarType {
let x: Int
let y: Int
public init (_jsonValue value: JSONValue) throws {
guard let value = value as? String,
let coordinates = value.components(separatedBy: ",").compactMap({ Int($0) }),
coordinates.count == 2 else {
throw JSONDecodingError.couldNotConvert(value: value, to: Coordinate.self)
}
self.x = coordinates[0]
self.y = coordinates[1]
}
public var _jsonValue: JSONValue {
"\(x),\(y)"
}
}
}

缓存键配置

在Apollo iOS 0.x版本中,您可以通过为ApolloClient提供cacheKeyForObject块来配置规范化缓存的缓存键计算。

在Apollo iOS 1.0中,我们用SchemaConfiguration.swift文件中的类型安全API替换此功能,该文件由Apollo iOS与生成的模式类型一起生成。

要将缓存键配置代码进行迁移,将cacheKeyForObject实现重构到SchemaConfiguration.swift文件的cacheKeyInfo(for type:object:)功能中。

在0.x中,我们建议您在缓存键中使用对象的__typename前缀来防止键冲突。

Apollo iOS 1.0会自动执行此操作。如果您想根据不同类型的对象(例如,根据通用接口类型)对缓存键进行分组,您可以设置返回的CacheKeyInfouniqueKeyGroup属性。

有关新缓存键配置API的更多详细信息,请参阅自定义缓存键

示例

给定一个cacheKeyForObject块:

client.cacheKeyForObject = {
guard let typename = $0["__typename"] as? String,
let id = $0["id"] as? String else {
return nil
}
return "\(typename):\(id)"
}

您可以将此迁移到新的cacheKeyInfo(for type:object:)函数,如下所示:

SchemaConfiguration.swift
public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration {
static func cacheKeyInfo(for type: Object, object: JSONObject) -> CacheKeyInfo? {
guard let id = object["id"] as? String else {
return nil
}
return CacheKeyInfo(id: id)
}
}

或者您可以使用JSON值便捷初始化器,如下所示:

SchemaConfiguration.swift
public enum SchemaConfiguration: ApolloAPI.SchemaConfiguration {
static func cacheKeyInfo(for type: Object, object: JSONObject) -> CacheKeyInfo? {
return try? CacheKeyInfo(jsonValue: object["id"])
}
}

本地缓存变异

在Apollo iOS的0.x版本中,您可以直接使用任何生成的操作或片段模型在本地缓存中更改数据。

直接缓存访问的API基本上保持不变,但生成的模型对象现在是默认不可变。您仍然可以使用生成的模型直接读取缓存数据,但为了变异缓存数据,您现在需要定义单独的本地缓存变异操作或

您可以通过将@apollo_client_ios_localCacheMutation指令应用于任何GraphQL操作或fragment定义来创建本地缓存变异模型。

有关新的本地缓存变异API的详细说明,请参阅直接缓存访问

将缓存变异与网络操作分离

将一个标记为LocalCacheMutation,该缓存变异的生成模型不再符合GraphQLQuery。这意味着您不能再将其缓存变异用作查询操作。

从根本上讲,这是因为缓存变异模型是可变的,而网络响应数据是不可变的。缓存变异是为访问和修改必要数据而设计的。

如果我们的缓存变异模型是可变的,则在ReadWriteTransaction之外变异它们不会将任何更改保存到缓存中。此外,可变数据模型需要生成的代码量几乎翻倍。通过保持不可变模型,我们避免了这种混淆并减少了我们的生成代码。

避免创建整个查询操作的可变版本。相反,定义可变异的片段或查询来变异所需的

示例

给定一个操作和从Apollo iOS 0.x版本写入的交易:

query UserDetails {
loggedInUser {
id
name
posts {
id
body
}
}
}
store.withinReadWriteTransaction({ transaction in
let cacheMutation = UserDetailsQuery()
let newPost = UserDetailsQuery.Data.LoggedInUser.Post(id: "789, body: "This is a new post!")
try transaction.update(cacheMutation) { (data: inout UserDetailsQuery.Data) in
data.loggedInUser.posts.append(newPost)
}
})

在Apollo iOS 1.0中,您可以使用新的LocalCacheMutation来重写这段代码:

query AddUserPostLocalCacheMutation @apollo_client_ios_localCacheMutation {
loggedInUser {
posts {
id
body
}
}
}
store.withinReadWriteTransaction({ transaction in
let cacheMutation = AddUserPostLocalCacheMutation()
let newPost = AddUserPostLocalCacheMutation.Data.LoggedInUser.Post(data: DataDict(
["__typename": "Post", "id": "789", "body": "This is a new post!"],
variables: nil
))
try transaction.update(cacheMutation) { (data: inout AddUserPostLocalCacheMutation.Data) in
data.loggedInUser.posts.append(newPost)
}
})

可空的输入值

根据GraphQL规范,显式地提供null作为输入字段的值与未提供值(nil)在语义上是不同的。

为了区分 nullnil,Apollo iOS 0.x版本将可选输入值生成了双重可选值类型(??,或 Optional<Optional<Value>>)。这对于许多用户来说很困惑,并且没有清楚地表达API的意图。

在Apollo iOS 1.0中,我们用新的GraphQLNullable包装枚举类型来替换双重可选值。

此新类型需要您明确指示输入字段的值或可空性行为。

虽然这个API稍微啰嗦一些,但它提供了清晰度,并减少了由意外行为引起的错误。

关于如何使用 GraphQLNullable的更多示例和最佳实践,请参阅使用可空参数

示例

如果我们向可空输入参数传递一个值,我们需要用 GraphQLNullable包裹该值:

Apollo iOS 0.x
MyQuery(input: "Value")
Apollo iOS 1.0
MyQuery(input: .some("Value"))

要提供一个 nullnil 值,请分别使用 .null.none

Apollo iOS 0.x
/// A `nil` double optional value translates to omission of the value.
MyQuery(input: nil)
/// An optional containing a `nil` value translates to an `null` value.
MyQuery(input: .some(nil))
Apollo iOS 1.0
/// A `GraphQLNullable.none` value translates to omission of the value.
MyQuery(input: .none)
/// A `GraphQLNullable.null` value translates to an `null` value.
MyQuery(input: .null)

当将可选值传递给可空输入值时,如果您的值是 nil,则需要提供默认值:

Apollo iOS 0.x
var optionalInput: String? = nil
MyQuery(input: optionalInput)
Apollo iOS 1.0
var optionalInput: String? = nil
MyQuery(input: optionalInput ?? .null)

模拟操作模型进行测试

在 Apollo iOS 的 0.x 版本中,您可以通过使用每个模型生成的初始化器或直接使用 JSON 数据初始化它们来创建生成的操作模型的模拟。这两种方法都容易出错,步骤繁琐且脆弱。

Apollo iOS 1.0 提供了一种根据您的模式类型生成测试模拟的新方法。首先,将 output.testMocks 添加到您的代码生成配置中,然后将您的测试模拟链接到您的单元测试目标。

而不是使用类型的生成初始化器创建模型,您可以创建底层数据的方案类型的测试模拟。使用测试模拟,您可以设置相关 字段 的值,并初始化您的 操作模型。

Apollo iOS 1.0 的新测试模拟更易于理解且类型安全。它们还消除了为不同模型类型生成初始化器的需求。

请注意,您仍然可以使用 JSON 数据初始化您的操作模型,但初始化器已稍有变化。更多详细信息,请参阅 JSON 初始化器

有关更多详细信息,请参阅 测试模拟

示例

给定一个 Hero 接口类型,它可以是一个 HumanDroid 类型,以下为 operation 的定义:

query HeroDetails {
hero {
id
... on Human {
name
}
... on Droid {
modelNumber
}
}
}

Apollo iOS 的 0.x 版本为 HeroDetails.Data.Hero 模型上的每个类型生成初始化器:

struct Hero {
static func makeHuman(id: String, name: String) {
// ...
}
static func makeDroid(id: String, modelNumber: String) {
// ...
}
}

这些初始化器在 Apollo iOS 1.0 中没有生成。相反,您可以直接初始化 Mock<Human>Mock<Droid>

let mockHuman = Mock<Human>()
mockHuman.id = "10"
mockHuman.name = "Han Solo"
let mockDroid = Mock<Droid>()
mockDroid.id = "12"
mockDroid.modelNumber = "R2-D2"

然后,使用您的测试模拟创建 HeroDetails.Data.Hero 模型的模拟:

let humanHero = HeroDetails.Data.Hero(from: mockHuman)
let droidHero = HeroDetails.Data.Hero(from: mockDroid)
从 JSON 数据生成测试模拟

如果您希望继续直接使用 JSON 数据初始化模型,请更新初始化器以使用 init(data: DataDict) 初始化器创建您的模型。您还必须确保您的 JSON 数据为 [String: AnyHashable] 字典。

0.x
let json: [String: Any] = [
"__typename: "Human",
// ...
]
let hero = HeroDetails.Data.Hero(
unsafeResultMap: json
)
1.0
let json: [String: AnyHashable] = [
"__typename: "Human",
// ...
]
let hero = HeroDetails.Data.Hero(
data: DataDict(json)
)
上一页
SDK 组件
下一页
v1.2
评价文章评价在GitHub上编辑编辑论坛Discord

©2024银河图计算法公司,商业名称为Apollo GraphQL。

隐私策略

公司