一、背景
GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你API中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
Apollo iOS是一款GraphQL的开源框架,使用Swift语言编写,它能帮助我们向GraphQL执行查询变更等操作,并且将结果以Swift类的形式返回给我们。
二、调研目标
帮助iOS项目进行Apollo iOS的集成,了解Apollo iOS在iOS开发中的使用,实现GraphQL接口的调用。
三、操作步骤
1. 集成SDK
- 安装或更新CocoaPods
因为 Apollo iOS 使用 Swift 5,所以需要使用 CocoaPods 1.7.0 或更高版本。 您可以使用以下命令安装 CocoaPods:
gem install cocoapods
- 添加依赖项
添加 pod "Apollo" 到你的Podfile文件中
添加 pod "Apollo/SQLite" 来使用ApolloSQLite
添加 pod "Apollo/WebSocket" 来使用ApolloWebSocket
2. Schema下载
- 创建脚本,拖动到Dependencies和Compile Sources之间
- 下载Schema脚本
SCRIPT_PATH= "${PODS_ROOT} /Apollo/scripts"
cd "${SRCROOT} /${TARGET_NAME}"
"${SCRIPT_PATH}" /run-bundled-codegen.sh schema:download --endpoint=endpointUrl schema.json
- 编译iOS程序,在项目目录中自动生成Schema.json
3.生成代码
- 创建一个graphql文件,输入GraphQL操作代码。以下我以一个登录接口为例:
query Login($identity: LoginIdentity) {
login(identity: $identity) {
code
firstName
lastName
mobile
}
}
- 使用代码生成脚本,生成Swift代码
"${SCRIPT_PATH}"/run-bundled-codegen.sh codegen:generate --target=swift --includes=./**/*.graphql --localSchemaFile="schema.json" API
脚本最后的API我们也可以用API.swift代替
${SCRIPT_PATH}"/run-bundled-codegen.sh codegen:generate --target=swift --includes=./**/*.graphql --localSchemaFile="schema.json" API.swift
两者的区别,使用API.swift,代码生成的所有内容将全部汇集到一个文件,如果请求的体量特别大的话,单个文件的代码量势必会变得非常大,影响编译速度;而使用API,生成代码的时候将以graphql操作文件进行拆分自动生成,但我们要提前在项目文件夹下新建一个名字为API的文件夹。最后生成的目录结构如下:
四、使用
1. 创建一个网络请求单例
import Foundation
import Apollo
class Network {
static let shared = Network()
//缓存类
private lazy var store : ApolloStore = {
let cache = InMemoryNormalizedCache()//默认的缓存策略,将数据缓存在内存中;还有一种SQLiteCache的缓存方式通过数据库将数据保存在磁盘 pod 'Apollo/SQLite'
return ApolloStore(cache: cache)
}()
//RequestChainNetworkTransport是一个标准的HTTP请求与服务器进行通信,可以做一个传输前后的交互
private lazy var normalTransport :RequestChainNetworkTransport = {
let authPayload = ["Content-Type": "application/json", "Accept":"application/json" ]//headrs数组
let client = URLSessionClient()//URL会话
let provider = NetworkInterceptorProvider(store: store, client: client)//拦截器容器
let endpointURL = URL(string: "endPointUrl")!//服务器地址
return RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: endpointURL, additionalHeaders: authPayload)//网络请求链
}()
//WebSocketTransport webSocket应用层协议
private lazy var webSocketTransport: WebSocketTransport = {
let url = URL(string: "wss://apollo-fullstack-tutorial.herokuapp.com/graphql")!
let request = URLRequest(url: url)
let webSocketClient = WebSocket(request: request)
return WebSocketTransport(websocket: webSocketClient)
}()
//支持RequestChainNetworkTransport和WebSocketTransport的网络传输类
private lazy var splitNetworkTransport = SplitNetworkTransport(
uploadingNetworkTransport: self.normalTransport,
webSocketNetworkTransport: self.webSocketTransport
)
private(set) lazy var client = ApolloClient(networkTransport: self.splitNetworkTransport, store: self.store)
}
//拦截器提供者
struct NetworkInterceptorProvider: InterceptorProvider {
private let store: ApolloStore
private let client: URLSessionClient
init(store: ApolloStore,
client: URLSessionClient) {
self.store = store
self.client = client
}
func interceptors<Operation: GraphQLOperation>(for operation: Operation) -> [ApolloInterceptor] {
//这些是Apollo内置的一些具有特定功能的拦截器,一般项目这些拦截器已经够用了,如果有特殊需求,可以往里加自定义的拦截器
// let max = MaxRetryInterceptor(maxRetriesAllowed: 5)
return [
MaxRetryInterceptor(),//最大重试
CacheReadInterceptor(store: **self**.store),//缓存读取
AddTokenInterceptor(),//custom - add token
// RequestLoggingInterceptor(),//custom - print request info
NetworkFetchInterceptor(client: **self**.client),//发送HTTP请求
ResponseCodeInterceptor(),//对未成功执行的操作,检查GraphQL服务器响应的响应代码,传递给错误回调
JSONResponseParsingInterceptor(cacheKeyForObject: **self**.store.cacheKeyForObject),//json解析为GraphQLResult对象,通过HTTPResponse回调出去
AutomaticPersistedQueryInterceptor(),//
// UserManagementInterceptor(),//custom - error code
CacheWriteInterceptor(store: **self**.store)//将请求返回的数据写入ApolloiOS的缓存中
]
}
}
2. 网络请求示例
- query
showLoading()
let Network = Network.shared
let identity = LoginIdentity.init(account: "123", password: "123", tenantCode: "123", type: "123", exclusive: true)
Network.client.fetch(query: LoginQuery.init(identity: identity)){[weak self] result in
self?.hideLoading()
switch result {
case .success(let graphQLResult):
print("Success: \(graphQLResult)")
case .failure(let error):
print("Failure! Error: \(error)")
}
}
- mutation
Network.client.perform(mutation: CancelOrderMutation.init(orderCode: "123")) { [weak self] result in
switch result {
self?.hideLoading()
case .success(let graphQLResult):
print("Success: \(graphQLResult)")
case .failure(let error):
print("Failure! Error: \(error)")
}
}
五、核心类
1. ApolloClient
ApolloClient遵循了ApolloClientProtocol实现了Apollo的核心API。
2. ApolloStore
Apollo iOS 不仅仅是针对 GraphQL 服务器运行查询。 它会将查询结果标准化,来构建数据的客户端缓存,随着进一步的查询和突变的运行,该缓存会保持最新。
- InMemoryNormalizedCache:这包含在主 Apollo 库中,是 Apollo 客户端的默认缓存策略。 这会将标准化结果存储在内存中,因此结果不会在应用程序的会话中持久保存。
- SQLiteCache:这是通过 ApolloSQLite 库包含的。 这会将缓存结果写入 SQLite 文件,而不是将结果保存在内存中。 请注意,这反过来会导致缓存命中进入磁盘,这可能会导致响应速度稍慢。 但是,这也减少了无限内存增长的机会,因为所有内容都被转储到磁盘。
3. NetworkTransport
负责将 GraphQL 操作发送到服务器。
- RequestChainNetworkTransport:NetworkTransport的实现,它为通过它发送的每个对象创建一个RequestChain对象,使用标准的HTTP通信
- WebSocketTransport:通过webSocket应用层协议可以实现订阅功能
- SplitNetworkTransport:分裂式网络传输,能够同时实现RequestChainNetworkTransport + WebSocketTransport的功能
4. InterceptorProvider
拦截器提供者,管理着请求链上的一系列拦截器,并且配合着解析和缓存系统一起工作。它的作用尽可能的还原了HTTPNetworkTransport的体验。它需要URLSessionClient和ApolloStore来进行初始化,目的是将他们传递给受管理的拦截器使用。
- build-in:Apollo提供了一系列拦截器,来实现一个完整的请求,以及请求结果的处理。
- custom:我们可以通过继承ApolloInterceptor来自定义一个拦截器。以下示例自定义了一个拦截器,在请求头中添加token:
import Apollo
class AddTokenInterceptor: ApolloInterceptor {
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void) {
request.addHeader(name: "AUTH-TOKEN", value: "12345678")
chain.proceedAsync(request: request,
response: response,
completion: completion)
}
}
六、请求流水线
当一个GraphQL操作被执行时,InterceptorProvider创建RequestChain,RequestChain定义了一系列拦截器,用于处理特定GraphQL操作的生命周期。然后在请求链上调用kickoff函数,来运行链中的第一个拦截器,当拦截器执行完它的逻辑以后,在请求链上调用proceedAsync函数,来执行下一个拦截器的逻辑,直到最后一个拦截器执行完成,最后处理结果。
结语
Apollo iOS使用强类型,我们无需处理解析 JSON 响应或传递需要手动转换的值的字典。也不需要自己编写模型类型,模型是由UI定义的GraphQL操作生成的。
Apollo iOS标准化操作结果以构建数据的客户端缓存,该缓存会随着执行的每个操作而更新。这意味着UI始终在内部保持一致,并且可以通过尽可能少的操作与后端保持同步。
得益于这两点,项目的开发周期能够得到有效缩短。