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. Apollo iOS v1.x系の変更で

    インパクトがある点をおさらいする
    2023/10/17: Mobile勉強会#11 @ Wantedly様
    Fumiya Sakai

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  5. 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表現の考察

    View full-size slide

  6. 今回は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サーバー開発を含めたサンプル開発を一通り把握しておきたいと思いました。

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. 今回はGraphQLを利用した簡単なサンプル準備しました
    今回は2種類のGraphQLクライアントからQuery & Mutation処理をするものです
    サンプルの概略とGitHubのご紹介:
    Example1 Example2
    GitHub Repository:

    https://github.com/fumiyasac/SimpleGraphQLPractice
    2種類のサンプルにおける違いに関する補足:
    Example1は公開API / Example2はLocalサーバー利用

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. 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するイメージ

    View full-size slide

  20. 実際にどの様な形で取得データが入っているか?
    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]の入れ子構造
    👉 構造が複雑だと解析が大変…

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. Thank you for listening !

    View full-size slide