Slide 1

Slide 1 text

Apollo iOS v1.x系の変更で インパクトがある点をおさらいする 2023/10/17: Mobile勉強会#11 @ Wantedly様 Fumiya Sakai

Slide 2

Slide 2 text

自己紹介 ・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

Slide 3

Slide 3 text

今年のiOSDCはスポンサーセッション登壇&原稿2本

Slide 4

Slide 4 text

iOSのUI実装本を執筆しています! 書籍に掲載したサンプルのバージョンアップや続編等に現在着手中です。 少しの工夫で実現できるTIPS集やライブラリ表現の活用集をはじめとした、iOSア プリ開発の中でも特にUI実装やUIKitを利用した画面の中で特徴を与える様な表現 という題材に焦点を当てた書籍となっております。 現在は電子書籍版のみとなります。 こちらは全て¥1,000となっております。 https://just1factory.booth.pm/ 概要: https://book-tech.com/ 価格: 📖 Booth 📖 Book Tech

Slide 5

Slide 5 text

UI実装であると嬉しいレシピブックの最新情報 UI実装であると嬉しいレシピブックVol.3として昨年10月に商業化しました! Still WIP これまでの同人誌として頒布したものに加えて、Vol.1及びVol.2に頒布したものの 中で書籍に載せきれなかったものや表現や動きが特徴的でユーザーにもほんの少し 遊び心を与える様なUI実装を紹介したものをVol.3としています。 概要: これからの構想: こちらで購入可能です: Amazon / Google Play / Apple Books / KINOKUNIYA / Rakuten BOOKS etc.. 🏊 iOS: SwiftUIを利用したUI実装や動画関連の実装 🏊 Android: Jetpack Composeの基本やその他気になるUI表現の考察

Slide 6

Slide 6 text

今回はGraphQLとapollo-iosに関連する話題です apollo-iosをv0.x系からv1.x系へバージョンアップを実施した際の振り返り 想定以上に破壊的な変更となっていた部分もありましたので、今回はインパクトが大きい点をピックアップしています。 1. 業務でもapollo-iosをv1.x系へバージョンアップを行った: サーバーサイド側をGraphQLへ寄せていく方針としているため、Apolloでの処理がアプリの根幹を担っていました。 よってv1.1以降にはバージョンアップの頻繁が高いことや最新バージョンへ追従をするため対応を実施しました。 2. 改めてMigrationを実施するとv0.x系から設定関連で大きく変更があった: apollo-iosの導入から必要なコード生成に関する手順においてv0.x系から破壊的な変更があったので、v1.x系にバージョンアッ プを図る際に気をつけた方が良さそうな点をこの機会に改めて整理しておきたいと感じました。 3. 実際に動作する簡単なサンプル開発を含めて試しておきたいと感じていた: 業務でもGraphQLを利用していたこともあり、業務におけるドキュメント作成や今後の個人開発での利用も見越し、自分でも簡単 なGraphQLサーバー開発を含めたサンプル開発を一通り把握しておきたいと思いました。

Slide 7

Slide 7 text

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の基本に関して: 基本的な用法や使い方等に関してはこちらを参照。 今回は特にマイグレーションに関連する部分を中心にご紹介ができればと思います。 今回ご紹介するメイン

Slide 8

Slide 8 text

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を実行するための処理を記載する必要があります。

Slide 9

Slide 9 text

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」の前に記述する

Slide 10

Slide 10 text

apollo-iosのv1.0系以降における設定関連のポイント apollo-ios-cliを利用する形へ変更されたのでより単体で動作する形になった CLIインストール手順: ApolloをSwiftPMで導入後、記載したQueryからのコード自動生成ができる様にする下準備。 ① プロジェクト部分を右クリックする ② Install CLIをクリックする ③ Project Rootを選択しRunを押下 ④ Allow Command to Change Filesを押下

Slide 11

Slide 11 text

apollo-ios-cliの設定に関する部分のポイント(1) apollo-ios-cli.jsonを生成し当該プロジェクトでの自動生成内容を記載する GraphQL用コード生成: apollo-codegen-config.jsonを配置してコード生成コマンド実行時の生成内容に関する設定をする必要があります。 input定義例 schema.jsonや.graphqlファイルの配置場所に関する定義を記述 operationSeachPaths: schemaSearchPaths: schema.jsonの配置場所を指定する QueryやMutationを定義した ⚫︎⚫︎⚫︎ .graphqlファイルの配置場所を指定する

Slide 12

Slide 12 text

apollo-ios-cliの設定に関する部分のポイント(2) apollo-ios-cli.jsonを生成し当該プロジェクトでの自動生成内容を記載する output定義例 コマンド実行後に生成されたコードを格納場所に関する定義を記述 ※ こちらはSingle Module時での指定例になります name: ModuleName / accessModifier: public Multi Module構成の場合は下記の様な設定をする

Slide 13

Slide 13 text

apollo-ios-cliの設定に関する部分のポイント(3) apollo-ios-cli.jsonを生成し当該プロジェクトでの自動生成内容を記載する shemaDownloadConfiguration定義例 コマンドラインからダウンロードするschemaファイルに関する定義を記述 ① SchemaをDLする: $ ./apollo-ios-cli generate $ ./apollo-ios-cli fetch-schema 複雑なProjectではより詳細な設定が必要になりますが、最低限押さえておきたい箇所をピックアップしました。 ② コードを自動生成する:

Slide 14

Slide 14 text

今回はGraphQLを利用した簡単なサンプル準備しました 今回は2種類のGraphQLクライアントからQuery & Mutation処理をするものです サンプルの概略とGitHubのご紹介: Example1 Example2 GitHub Repository: https://github.com/fumiyasac/SimpleGraphQLPractice 2種類のサンプルにおける違いに関する補足: Example1は公開API / Example2はLocalサーバー利用

Slide 15

Slide 15 text

Example1のGraphQLのエンドポイント紹介 世界の国情報(通貨情報・電話番号・国旗の絵文字等)を取得するAPI 国情報を取得するEndpoint: https://countries.trevorblades.com/graphql

Slide 16

Slide 16 text

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で取り扱 いができる様に生成されたコード

Slide 17

Slide 17 text

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処理についても、登録した様に見せかけたダミー処理です。

Slide 18

Slide 18 text

サンプル内での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)部分では、リクエスト状態に応じた画面内容の表示処理を実施

Slide 19

Slide 19 text

GraphQL側の処理でasync/awaitを利用するコード例 // GraphQLのQuery処理をasync/awaitの処理内で実行する @discardableResult func fetchAsync( query: Query, cachePolicy: CachePolicy = .default, contextIdentifier: UUID? = nil, queue: DispatchQueue = .main ) async throws -> GraphQLResult { // 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: Mutation, publishResultToStore: Bool = true, queue: DispatchQueue = .main ) async throws -> GraphQLResult { // 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するイメージ

Slide 20

Slide 20 text

実際にどの様な形で取得データが入っているか? final class CountryListRequestSuccessMock: CountryListRequest { // MARK: - Function func getResult() async throws -> GraphQLResult { // 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]の入れ子構造 👉 構造が複雑だと解析が大変…

Slide 21

Slide 21 text

補足. 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❤ + =

Slide 22

Slide 22 text

まとめ apollo-iosのバージョンアップは破壊的ではあったが今後もキャッチアップする 平素の業務においても活用していた経緯もあり、今回は復習の意味も兼ねて「基本のき」をご紹介した次第です。 1. CLIやコード自動生成部分については大きな変更があった: v0.x系と比べた場合、CLIの準備やコード自動生成に関連する処理はかなり大きな変更があった部分でした。大規模なProject等 で移行作業が必要になる場合は地味に大変な部分もあるかと思いました。 2. 内部処理に関しても変更があるのでその点には注意を: GraphQLから取得したデータをEntityへ変換する処理自体にはこれまで通りの流れではあるものの、UnitTestで利用するStubを定 義する場合の様に、想定するレスポンスの形を実現する場合には少し煩雑に感じるかもしれません。 3. 現在実務や個人サンプル開発等でも活用していてとても良いと感じる場面が多い: 業務内でもサーバーサイドエンジニア側とのコミュニケーションを図る場面や機能開発を進める場合においても、スピードアッ プが見込める実感があるので、以前サーバーサイドエンジニアをした経験も活かしながら良い活路を模索したいです。

Slide 23

Slide 23 text

Thank you for listening !