Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Apollo iOS v1.x系の変更でインパクトがある点をおさらいする

Apollo iOS v1.x系の変更でインパクトがある点をおさらいする

Mobile勉強会 Wantedly × チームラボ #11での登壇資料になります。

今回はGraphQL関連のトピックとして、「apollo-ios」をv1.x系以降にバージョンアップした際に個人的にインパクトがあった点やマイグレーション関連のTIPS等をご紹介しています。

現在、業務でもサーバーサイド側の処理はGraphQLを中心に据えていくための取り組みをしていた事や個人的な開発でも今後の有効活用をしていきたいと感じたので、業務内で得られた知見や簡単なサンプル開発を通じて得られた学びの中で「基本のき」の部分をまとめたものになります。今後の参考になれば幸いに思います。

Fumiya Sakai

October 17, 2023
Tweet

More Decks by Fumiya Sakai

Other Decks in Technology

Transcript

  1. 自己紹介 ・Fumiya Sakai ・Mobile Application Engineer アカウント: ・Twitter: https://twitter.com/fumiyasac ・Facebook:

    https://www.facebook.com/fumiya.sakai.37 ・Github: https://github.com/fumiyasac ・Qiita: https://qiita.com/fumiyasac@github 発表者: ・Born on September 21, 1984 これまでの歩み: Web Designer 2008 ~ 2010 Web Engineer 2012 ~ 2016 App Engineer 2017 ~ Now iOS / Android / sometimes Flutter
  2. apollo-iosのMigration関連&GraphQLの基本確認資料 英語ドキュメントが多く感じるものの情報については充実している印象 公式Document内のMigrationガイド: 他にv1.2 & v1.3へのMigrationガイドもあります。 https://www.apollographql.com/docs/ios/migrations/1.0 Apollo iOS 1.0

    migration guide : https://graphql.org/learn/queries/ Queries and Mutations : Query & Mutatuionの基本に関して: 基本的な用法や使い方等に関してはこちらを参照。 今回は特にマイグレーションに関連する部分を中心にご紹介ができればと思います。 今回ご紹介するメイン
  3. apollo-iosの過去バージョン時における設定関連の復習 特に導入部分の変更点を見極めるためにv0.x系の利用時の特徴や設定を確認 事前準備に関する変更点: 例. コード自動生成処理 Install CLI: Build Phase in

    Xcode Project: Download Schema: 例. CLI v0.x系はそもそもApolloのCLI(コマンドラインツール)のインストールにnode.jsが必要でした。 $ npm install -g apollo $ apollo schema:download —endpoint==https://example.com/graphql コードの自動生成処理はBuild実施時に一緒に実行する形にしていました。 Xcode内のBuild Phase内にapollo-iosで提供しているscriptを実行するための処理を記載する必要があります。
  4. apollo-iosの過去バージョン時のProject内部構成例 Project内部に配置したschema.jsonと.graphqlファイルからの自動生成処理抜粋 DERIVED_DATA_CANDIDATE="${BUILD_ROOT}" while ! [ -d "${DERIVED_DATA_CANDIDATE}/SourcePackages" ]; do

    if [ "${DERIVED_DATA_CANDIDATE}" = / ]; then echo >&2 “Error Message about SourcePackages: '${BUILD_ROOT}'" exit 1 fi DERIVED_DATA_CANDIDATE="$(dirname "${DERIVED_DATA_CANDIDATE}")" done SCRIPT_PATH="${DERIVED_DATA_CANDIDATE}/SourcePackages/checkouts/apollo-ios/scripts" if [ -z "${SCRIPT_PATH}" ]; then echo >&2 “Error Message about CLI script location.” exit 1 fi cd “${SRCROOT}/${TARGET_NAME}" "${SCRIPT_PATH}"/run-bundled-codegen.sh codegen:generate --target=swift -- includes=./**/*.graphql --localSchemaFile="schema.json" ./GraphQL/API.swift GraphQL関連部分の抜粋: ※ Build Phase記載内容に関する注意事項 .graphqlを元に生成したコード ダウンロードしたスキーマ定義 事前にSwiftPMでapollo-iosを導入: 右側の記載内容が実行処理例になります。 ① SourcePackagesの確認: ② CLIの確認: ③ コード自動生成処理の実行: schema定義からの生成内容は API.swiftに記載 順番は「Compile Sources」の前に記述する
  5. Example2のGraphQL側処理の概要と自動生成内容 Apollo-Server製のLocal環境と生成されるSwiftファイルとの関係性 DLしたSchema定義json この様な形でQuery & Mutationに応じた内容が生成 ① SchemaをDLする: $ ./apollo-ios-cli

    generate $ ./apollo-ios-cli fetch-schema ② コードを自動生成する: 自分で設定した部分の生成ファイル: (定義名).graphql.swift Schema定義に基づく生成ファイル: 定義したQuery / Mutationを元に Swiftで処理する生成されたコード ••.graphql.swift schema定義の処理をSwiftで取り扱 いができる様に生成されたコード
  6. Example2のGraphQL側処理とエンドポイントとの対応 今回のサンプル処理におけるArrayで定義されたデータ一覧を返却する例 Swift側でのEntity変換処理例 News一覧Query定義 お知らせ一覧を返却する処理: Apollo-Server内の処理例: 内部定義データを返却する処理 // ① News一覧を取得する

    getNews: (parent: any, args: any, context: any) => { // Contextから渡されたNews一覧データをGraphQLで返却するための処理 const result = context.news; return result; }, query getAllNews { getNews { id title date genre } } result.data?.getNews?.compactMap { NewsEntity( id: $0.id, title: $0.title, date: $0.date, genre: $0.genre ) } ?? [] Example2で利用しているGraphQLサーバーに関する補足: このGraphQLサーバーは「Apollo-Server + TypeScript」で作成しています。 ※1. DBとの接続はしていないのでQueryでの取得内容は内部定義しています。 ※2. Mutation処理についても、登録した様に見せかけたダミー処理です。
  7. サンプル内でのGraphQLとの疎通処理部分の概要 基本的にはasync/awaitを利用したMVVMパターン構成とUIとの双方向Binding View(Screen) ViewModel Repository Request GraphQLClient 全体処理と各種責務担当クラスの概要: async/awaitの処理から`@Published`へ反映する async/waitの処理を利用して対応Entityへ変換する

    1. Request⇔Repository部分では、[String: AnyHashable]型のデータを表示用のEntityへマッピングし直す処理を実施 処理部分におけるポイント: 2. Repository⇔ViewModel部分では、View表示で利用する`@Published`で定義した変数へのハンドリング処理を実施 3. ViewModel⇔View(Screen)部分では、リクエスト状態に応じた画面内容の表示処理を実施
  8. GraphQL側の処理でasync/awaitを利用するコード例 // GraphQLのQuery処理をasync/awaitの処理内で実行する @discardableResult func fetchAsync<Query: GraphQLQuery>( query: Query, cachePolicy:

    CachePolicy = .default, contextIdentifier: UUID? = nil, queue: DispatchQueue = .main ) async throws -> GraphQLResult<Query.Data> { // MEMO: withCheckedThrowingContinuationでErrorをthrowする形にしています。 return try await withCheckedThrowingContinuation { continuation in fetch( query: query, cachePolicy: cachePolicy, contextIdentifier: contextIdentifier, queue: queue ) { result in switch result { case .success(let value): continuation.resume(returning: value) case .failure(let error): continuation.resume(throwing: error) } } } } // GraphQLのMutation処理をasync/awaitの処理内で実行する @discardableResult func performAsync<Mutation: GraphQLMutation>( mutation: Mutation, publishResultToStore: Bool = true, queue: DispatchQueue = .main ) async throws -> GraphQLResult<Mutation.Data> { // MEMO: withCheckedThrowingContinuationでErrorをthrowする形にしています。 return try await withCheckedThrowingContinuation { continuation in perform( mutation: mutation, publishResultToStore: publishResultToStore, queue: queue ) { result in switch result { case .success(let value): continuation.resume(returning: value) case .failure(let error): continuation.resume(throwing: error) } } } } GraphQL処理をwithCheckedThrowingContinuationでWrappingするイメージ
  9. 実際にどの様な形で取得データが入っているか? final class CountryListRequestSuccessMock: CountryListRequest { // MARK: - Function

    func getResult() async throws -> GraphQLResult<CountriesSchema.GetAllCountriesQuery.Data> { // MEMO: Apollo1.x系からはGraphQLで返却されるデータをUnitTest用にマッピングする際には注意が必要(構造が複雑になりがち) // 一覧データのMock生成時の流れ // (1) まずDataDict型(第1引数は[String: AnyHashable]型、第2引数は空配列)のデータを作成してレスポンスデータを想定してマッピングをする // (2) 次にCountriesSchema.GetAllCountriesQuery.Data型のデータを作成してGraphQLResultに入れて返却する let dataDict = DataDict( data: [ "countries": [ DataDict(data: ["code": "MY", "name": "Malaysia", "emoji": "🇲🇾 "], fulfilledFragments: []), DataDict(data: ["code": "TH", "name": "Thailand", "emoji": "🇹🇭 "], fulfilledFragments: []), DataDict(data: ["code": "MX", "name": "Mexico", "emoji": "🇲🇽 "], fulfilledFragments: []), DataDict(data: ["code": "JP", "name": "Japan", "emoji": "🇯🇵 "], fulfilledFragments: []), DataDict(data: ["code": "IN", "name": "India", "emoji": "🇮🇳 "], fulfilledFragments: []) ] ], fulfilledFragments: [] ) let data = CountriesSchema.GetAllCountriesQuery.Data.init(_dataDict: dataDict) return GraphQLResult(data: data, extensions: [:], errors: nil, source: .server, dependentKeys: nil) } } query GetAllCountries { countries { code name emoji } } 一覧情報取得 QueryでのStub [String: AnyHashable]の入れ子構造 👉 構造が複雑だと解析が大変…
  10. 補足. GraphQLやApolloの雰囲気を掴むための参考資料 実際に導入した事例や過去のiOSDCでの資料が理解の助けになりました 登壇資料: https://speakerdeck.com/ymanya/swiftdemoapollo-iosdekuai-shi-nigraphql Swiftでもapollo-iosで快適にGraphQL https://speakerdeck.com/chocoyama/swiftuitographqltehurotakutofalseji-sok-de-napo-huai-nili-tixiang-kau SwiftUIとGraphQL でプ ロ

    ダ クトの継続的な破壊に立ち向かう 解説ブログ: https://zenn.dev/saboyutaka/articles/e5515872871534 GraphQLはいつ使うか、RESTとの比較 https://zenn.dev/saboyutaka/articles/07f1351a6b0049 GraphQLが解決する問題とその先のユースケース HAPPY❤ + =
  11. まとめ apollo-iosのバージョンアップは破壊的ではあったが今後もキャッチアップする 平素の業務においても活用していた経緯もあり、今回は復習の意味も兼ねて「基本のき」をご紹介した次第です。 1. CLIやコード自動生成部分については大きな変更があった: v0.x系と比べた場合、CLIの準備やコード自動生成に関連する処理はかなり大きな変更があった部分でした。大規模なProject等 で移行作業が必要になる場合は地味に大変な部分もあるかと思いました。 2. 内部処理に関しても変更があるのでその点には注意を: GraphQLから取得したデータをEntityへ変換する処理自体にはこれまで通りの流れではあるものの、UnitTestで利用するStubを定

    義する場合の様に、想定するレスポンスの形を実現する場合には少し煩雑に感じるかもしれません。 3. 現在実務や個人サンプル開発等でも活用していてとても良いと感じる場面が多い: 業務内でもサーバーサイドエンジニア側とのコミュニケーションを図る場面や機能開発を進める場合においても、スピードアッ プが見込める実感があるので、以前サーバーサイドエンジニアをした経験も活かしながら良い活路を模索したいです。