Apollo iOS集成

1,240 阅读6分钟

一、背景

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下载

- 创建脚本,拖动到DependenciesCompile Sources之间

截屏2022-02-28 14.54.40.png

image.png

截屏2022-02-28 11.08.25.png

- 下载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

截屏2022-02-28 11.10.52.png

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的文件夹。最后生成的目录结构如下:

截屏2022-02-28 11.36.06.png

四、使用

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。 截屏2022-02-28 13.44.11.png

2. ApolloStore

Apollo iOS 不仅仅是针对 GraphQL 服务器运行查询。 它会将查询结果标准化,来构建数据的客户端缓存,随着进一步的查询和突变的运行,该缓存会保持最新。 截屏2022-02-28 13.43.35.png

  1. InMemoryNormalizedCache:这包含在主 Apollo 库中,是 Apollo 客户端的默认缓存策略。 这会将标准化结果存储在内存中,因此结果不会在应用程序的会话中持久保存。
  2. SQLiteCache:这是通过 ApolloSQLite 库包含的。 这会将缓存结果写入 SQLite 文件,而不是将结果保存在内存中。 请注意,这反过来会导致缓存命中进入磁盘,这可能会导致响应速度稍慢。 但是,这也减少了无限内存增长的机会,因为所有内容都被转储到磁盘。

3. NetworkTransport

负责将 GraphQL 操作发送到服务器。
截屏2022-02-28 13.42.04.png

  1. RequestChainNetworkTransport:NetworkTransport的实现,它为通过它发送的每个对象创建一个RequestChain对象,使用标准的HTTP通信
  2. WebSocketTransport:通过webSocket应用层协议可以实现订阅功能
  3. SplitNetworkTransport:分裂式网络传输,能够同时实现RequestChainNetworkTransport + WebSocketTransport的功能

4. InterceptorProvider

拦截器提供者,管理着请求链上的一系列拦截器,并且配合着解析和缓存系统一起工作。它的作用尽可能的还原了HTTPNetworkTransport的体验。它需要URLSessionClient和ApolloStore来进行初始化,目的是将他们传递给受管理的拦截器使用。 截屏2022-02-28 13.42.33.png

  1. build-in:Apollo提供了一系列拦截器,来实现一个完整的请求,以及请求结果的处理。
  2. 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函数,来执行下一个拦截器的逻辑,直到最后一个拦截器执行完成,最后处理结果。

RequestPipeline.png

结语

Apollo iOS使用强类型,我们无需处理解析 JSON 响应或传递需要手动转换的值的字典。也不需要自己编写模型类型,模型是由UI定义的GraphQL操作生成的。
Apollo iOS标准化操作结果以构建数据的客户端缓存,该缓存会随着执行的每个操作而更新。这意味着UI始终在内部保持一致,并且可以通过尽可能少的操作与后端保持同步。
得益于这两点,项目的开发周期能够得到有效缩短。