Slide 1

Slide 1 text

株式会社サイバーエージェント 小幡 十矛 少数チームで挑む: SwiftUI, TCA, KMPを用いた 新規動画配信アプリ 「ABEMA Live」の開発について 2024/04/15 CA.swift #19 1

Slide 2

Slide 2 text

・2021年、株式会社サイバーエージェントに新卒入社。 ・2023年4月までAmebaブログのiOSアプリを作成。 ・2023年5月から「ABEMA Live」iOSアプリの開発リードをしています。 ・WWDC 2024現地参加予定!🇺🇸 名前:小幡 十矛(Obata Tomu) 自己紹介 https://x.com/_tomu28 https://github.com/tomu28 2

Slide 3

Slide 3 text

POINT 01 サービス説明 POINT 02 実装機能 POINT 03 開発体制, スケジュール POINT 04 iOS側の技術構成, 使用ライ ブラリ POINT 05 技術選定理由 POINT 06 開発Tips POINT 07 さいごに 目次 3

Slide 4

Slide 4 text

サービス説明 4

Slide 5

Slide 5 text

「ABEMA Live」では、「ABEMA」で配信されるアーティストのライブ、 イベント、スポーツ興行、舞台など多彩なペイパービュー (PayPerView)コンテンツを、海外からでも購入できます。 第一弾として世界最大級の格闘技団体・ONE Championshipの日本大会 をタイ、フィリピン、韓国にて同時生中継することが決定 https://www.onefc.com/jp/events/one165/ サービス説明 5

Slide 6

Slide 6 text

実装機能 6

Slide 7

Slide 7 text

ホーム/お知らせ/マイページ の3タブ構成 海外向け、PPV動画配信、 ログイン機能あり 実装機能 7

Slide 8

Slide 8 text

ユーザーログイン機能 ログイン アカウント登録 アカウント削除 ログインせずに使用する パスワードリセット 実装機能 8

Slide 9

Slide 9 text

ホーム 実装機能 9

Slide 10

Slide 10 text

番組詳細 実装機能 10

Slide 11

Slide 11 text

プレイヤー 実装機能 11

Slide 12

Slide 12 text

お知らせ一覧/詳細 実装機能 12

Slide 13

Slide 13 text

マイページ アカウントセッティング サービス概要 FAQ お問い合わせ ライセンスページ ログアウト 購入済み番組表示 → 番組詳細 実装機能 13

Slide 14

Slide 14 text

アプリ内課金 実装機能 14

Slide 15

Slide 15 text

その他 強制アップデート メンテナンスモード エラー画面 実装機能 15

Slide 16

Slide 16 text

その他 全画面タブレット対応 多言語対応(英語・タイ語・韓国語) Haptics feedback 実装機能 16

Slide 17

Slide 17 text

開発体制, スケジュール 17

Slide 18

Slide 18 text

2024/01/28に初回のイベントを実施 2023/10月までに基本的な機能を作り終え、 QA開始できる状態を目標に、開発を推進していた 2023/05から開発開始 開発体制としては、iOSエンジニア1~2人、Androidエンジニア1人 iOS、Android兼任1人 開発体制, スケジュール 18

Slide 19

Slide 19 text

iOS側の技術構成, 使用ライブラリ 19

Slide 20

Slide 20 text

VRT(pointfreeco/swift-snapshot-testing) KMP(Kotlin Multiplatform) TCA(The Composable Architecture) SPM(Swift Package Manager)でのライブラリ, モジュール管理 Feature単位でのマルチモジュール分割 プレイヤー周り:AVKit, UIKit。UIViewRepresentableを使い、UI側はフルSwiftUI Swift, SwiftUI, Swift Concurrency (iOS 16.6+) iOS側の技術構成, 使用ライブラリ 20

Slide 21

Slide 21 text

touchlab/SKIE vtourraine/AcknowList → ライセンス画面で使用 exyte/ScalingHeaderScrollView → 縮小する固定ヘッダーを備えたスクロール ビュー onevcat/Kingfisher siteline/swiftui-introspect apple/swift-format realm/SwiftLint iOS側の技術構成, 使用ライブラリ 21

Slide 22

Slide 22 text

moko-resources → 画像は各OS毎の取得に変更 moko-kswift → SKIEに置き換え KMP-NativeCoroutines → SKIEに置き換え String Catalog → KMP経由の文言取得に変更 現在は使っていないが一時期使っていたもの iOS側の技術構成, 使用ライブラリ 22

Slide 23

Slide 23 text

技術選定理由 23

Slide 24

Slide 24 text

1 メリット TCAを使用した理由 TCAに則って実装を進めることになるため、チームメンバー間で書き方のズレが少 なくなること KMP側でstateの値を更新するようにしても、iOS側でstateのbindやKMPのAction 発火、遷移周りの処理だったり、他ビューのAction伝搬などロジックもあるの で、TCAに乗っかった方が見通しが良くなる 2 許容したデメリット 開発当初はまだバージョンが0.x.x系ということもあり、推奨される書き方や破壊 変更に追従する必要があること → 開発途中で1.0.0がリリースされた 今後、メンバーが増減した時も対応しやすい 技術選定理由 24

Slide 25

Slide 25 text

TCA https://star-history.com/#pointfreeco/swift-composable- architecture&Date 技術選定理由 25

Slide 26

Slide 26 text

SKIEを使って、KMPで生成されたコードをswiftで扱いやすくする Presentation層のロジックまでKMP側で吸収 KMPで行っていること 技術選定理由 26

Slide 27

Slide 27 text

Presentation層の ロジックまでKMP側で吸収 KMPで行っていること 技術選定理由 27

Slide 28

Slide 28 text

Presentation層の ロジックまでKMP側で吸収 KMPで行っていること 技術選定理由 28

Slide 29

Slide 29 text

Presentation層の ロジックまでKMP側で吸収 KMPで行っていること 技術選定理由 29

Slide 30

Slide 30 text

Presentation層の ロジックまでKMP側で吸収 KMPで行っていること 技術選定理由 30

Slide 31

Slide 31 text

Presentation層の ロジックまでKMP側で吸収 KMPで行っていること 技術選定理由 31

Slide 32

Slide 32 text

suspend funやflowをSwiftで扱いやすくなる default case削除(sealed class/interfaceをenumとして扱えるようにする) SKIEを使って、KMPで生成されたコードをSwiftで扱いやすくする KMPで行っていること 技術選定理由 32

Slide 33

Slide 33 text

SKIEを使って、iOS側で定義していたenumを削除した例 技術選定理由 33

Slide 34

Slide 34 text

SKIEを使って、default caseを削除した例 技術選定理由 34

Slide 35

Slide 35 text

TCAとKMPの連携(アカウン ト新規登録画面の場合) 技術選定理由 35

Slide 36

Slide 36 text

TCAとKMPの連携(アカウン ト新規登録画面の場合) 技術選定理由 36

Slide 37

Slide 37 text

TCAとKMPの連携(アカウン ト新規登録画面の場合) 技術選定理由 37

Slide 38

Slide 38 text

元々、String Catalogを使っていたがKMP経由の 文言リソースアクセスに変更 まず、String Catalog を使っていた理由 脱SwiftGen出来る No.05 No.03 純正なので、Appleの改善に追 従出来ること No.04 No.04 非常にシンプルな形で文言管 理、多言語対応出来る No.01 No.01 生成物を分かりやすい形でGit 管理出来る No.0 No.05 今後対応言語が増えても追加 対応しやすい No.02 No.02 Xcode上でString Catalogが見 やすい No.06 技術選定理由 38

Slide 39

Slide 39 text

XcodeよりEdit -> Convert → To String Catalogs...で、 Localizable.stringsをLocalizable.xcstrings(String Catalog)に Migrateする。もしLocalizable.xcstringsが存在するため、Migrate 出来ないとエラーが出る場合、元々存在していた Localizable.xcstringsを削除後、Migrateする。 Localizable.stringsを更新する 文字列リソースのCSV(localize-strings.csv)を編集する String Catalogの更新手順 元々、String Catalogを使っていたがKMP経由の 文言リソースアクセスに変更 技術選定理由 39

Slide 40

Slide 40 text

元々、String Catalogを使っていたがKMP経由の 文言リソースアクセスに変更 先ほどの手順でString Catalogを 更新するデメリット マスターとなるCSVに文言変更がある度、 Convertを手動でする必要がある。ショートカ ットを当てればかなり楽なので、個人的には 許容 String Catalogに記載されているKey名に補完を 使えないので、Key名は直接入力するかコピー して入力する必要がある。TypeSafeではない 例: Text("Key名", bundle: .module) 技術選定理由 40

Slide 41

Slide 41 text

別アプリでも同じ文言を使いたい場合、それぞれconvertしないといけない。共通コンポ ーネントUI確認用のStyleCatalogAppと、同じ文言を使う必要はなかったため問題はな かった。 恐らくAppleの想定的にはconvertはstringsファイルからの移行時に一度のみ使って、今 後はString Catalogの機能で文言追加推奨していると推測。ただ、今回はCSVをMaster Dataとして扱うので、この部分はAppleの推奨する開発フローとは若干それてしまう。 デメリット 元々、String Catalogを使っていたがKMP経由の 文言リソースアクセスに変更 技術選定理由 41

Slide 42

Slide 42 text

moko-resourcesとは moko-resourcesは、Android、iOS、Web向けのKotlin Multiplatform プロジェクトにおいて、リソース(文字列、色、画 像、フォント)のアクセスを容易にするライブラリです moko-resourcesを辞めた主な理由 技術選定理由 https://github.com/icerockdev/moko-resources 42

Slide 43

Slide 43 text

iOSer的にクリティカルな不具合もメンテされていないのはリスク 画像の言語別出し分けは標準機能として提供されていなそうだった moko-resourcesを辞めた主な理由 技術選定理由 43

Slide 44

Slide 44 text

画像に各言語毎の文言埋め込むパターン 今後、ビジネス要件的に入ってくる可能性が高いという背景も考慮 画像の言語別出し分けは標準機能として提供されていなそうだった moko-resourcesを辞めた主な理由 技術選定理由 44

Slide 45

Slide 45 text

SwiftUIのViewだけを組む時のスピードが落ちる。VRTのしやすさ にも影響 Xcode Previewsで多言語のプレビューを同時に行う機能がある が、それらも使えない Xcode Previewのクラッシュ不具合(クリティカル)が長期間放置 されている:https://github.com/icerockdev/moko- resources/issues/396 iOSer的にクリティカルな不具合もメンテされていないの はリスク moko-resourcesを辞めた主な理由 技術選定理由 45

Slide 46

Slide 46 text

SwiftUIIntrospectでtabViewの 表示・非表示を切り替えるように しています。 swiftui-introspect 技術選定理由 46

Slide 47

Slide 47 text

SwiftUIIntrospectでtabViewの 表示・非表示を切り替えるように しています。 swiftui-introspect 技術選定理由 47

Slide 48

Slide 48 text

開発Tips 48

Slide 49

Slide 49 text

KMP側ロジックのデバッグ POINT 01 iOS側で意図しないレスポンスが返ってくるAPI があった時は、KMP側のRequest部分でKMPの ログライブラリであるNapierログを入れ、 Requestとして使っている値をXcode上に表示す る POINT 02 もしくは、Proxymanで通信を可視化して調査し ていました Tips 49

Slide 50

Slide 50 text

iOS側で意図しないレスポンスが返ってく るAPIがあった時は、KMP側のRequest部 分でKMPのログライブラリであるNapier ログを入れ、Requestとして使っている値 をXcode上に表示する KMP側ロジックのデバッグ Tips https://github.com/AAkira/Napier 50

Slide 51

Slide 51 text

KMP側ロジックのデバッグ 実機でしたら、ProxymanのiOS App を入れて特定のホストをフィルター表 示して確認 https://proxyman.io/posts/2021-10- 17-Getting-Started-With-Proxyman- For-iOS もしくは、Proxymanで通 信を可視化して調査してい ました https://proxyman.io/ Tips 51

Slide 52

Slide 52 text

実機でしたら、ProxymanのiOS Appを入れて特定のホストをフィルター表示して確認 https://proxyman.io/posts/2021-10-17-Getting-Started-With-Proxyman-For-iOS Debugビルドだと、Napier.d で手元のXcodeよりログ見れます が、Release ビルドだとNapier.d 出さないようにしているた め、Proxyman で確認していました。 Proxyman Tips 52

Slide 53

Slide 53 text

以下のようにScheme変更することでシミュレーター自体の設定を 変更しなくてもタイ語が表示される状態でRun出来ます。 App Region: Thailand App Language: Thia 変更点: Tips 53

Slide 54

Slide 54 text

以下のコードが必要 StoreKit 2でIAPイベントのロギングをする場合 Tips https://firebase.google.com/docs/analytics/measure-in-app-purchases?hl=ja#swift 54

Slide 55

Slide 55 text

TCAに則り、NavigationStackStoreをAppViewというルートとなるViewで 使用して遷移処理を組んでいた 問題なく処理は行えていたが 理想はタブ毎などの適切な粒度で NavigationStackStoreの使用をすること Tips 画面遷移 55

Slide 56

Slide 56 text

IAP購入時、ログインしているAppleアカウント(Sandbox環境の場合は、 Sandboxアカウント)の国情報によって、通貨表記が変わるという仕様 タイで登録している 場合は฿ 日本で登録している 場合は¥ Tips 56

Slide 57

Slide 57 text

パッケージ, モジュール構成 依存の流れとしては、MobileAppFeatureパッケージで MobileAppCoreパッケージを読み込むような形となる 04/ 共通する処理はMobileAppCoreパッケージに入れるようにしている 03/ Feature moduleはMobileAppFeatureとしてまとめて置いたほうが、TV対応な ども視野に入れた時、モジュールの責務が明確になるため良いと考えた。 TV対応する時は、TVAppFeatureパッケージを作るイメージ。 02/ MobileAppFeatureとMobileAppCoreパッケージを定義 01/ Tips 57

Slide 58

Slide 58 text

1~2ヶ月ほど続けると、Androidエンジニアの方もPRを出せるようになりました 毎日のPR振り返りで各PRを全員で見る会を行っていました iOSエンジニアとAndroidエンジニアの歩み寄り Tips 58

Slide 59

Slide 59 text

iOSエンジニアはSwift, Androidエンジニアは Kotlin側のレビューをしっかり行う iOSエンジニアとAndroidエンジニアの歩み寄り Tips 59

Slide 60

Slide 60 text

iOSエンジニアはSwift, Androidエンジニアは Kotlin側のレビューをしっかり行う iOSエンジニアとAndroidエンジニアの歩み寄り Tips 60

Slide 61

Slide 61 text

iOSエンジニアはSwift, Androidエンジニアは Kotlin側のレビューをしっかり行う iOSエンジニアとAndroidエンジニアの歩み寄り Tips 61

Slide 62

Slide 62 text

タブレット対応 Tips 62

Slide 63

Slide 63 text

タブレット対応 Tips 63

Slide 64

Slide 64 text

強制アプデ 不確実性への対応必須な部分とそうでは ない部分を都度切り分け、現実的なスケ ジュールで進められるよう適切に交渉。 ビジネス側や契約の都合で変化する恐れ がある部分は、審査不要で対応出来るよ うにFeatureFlagを差し込むなど柔軟にア プローチする。 POINT 04 使い所や適用範囲は要検討ですが、申請 不要でリアルタイムで制御出来るのは便 利。 POINT 03 不測の事態に備えて、FeatureFlagを入れ ておくことは大事 POINT 02 リアルタイム Remote Configでフラグ管 理して必要であれば、リアルタイム反映 するようにしました POINT 01 Tips 64

Slide 65

Slide 65 text

設定を変更した後に再度審査に出さないと処理が進まなかった 日本以外の配信国を追加した際、 年齢制限「なし」のままだと処理が進まない 審査時、ハマったポイント 65

Slide 66

Slide 66 text

Tips UIはフルSwiftUI, ロジックはKMPで吸収する 構成で、Crash数は0をキープ出来ています 66

Slide 67

Slide 67 text

POINT 01 共通ComponentはStyle Catalog Appで見れるようにし て、デザイナー確認 POINT 02 Haptics Feedback Tips 67

Slide 68

Slide 68 text

Haptics Feedback プレイヤーで5秒スキップ時 最も軽いlight feedbackを設定する ホーム面から「サムネイル画像」or 「Check This Event」タップし、番組 詳細面に遷移するとき 最も軽いlight feedbackを設定する 課金成功し、アイテムが更新された タイミング Appleが用意しているSuccess feedbackを設定 する Tips 68

Slide 69

Slide 69 text

Live Activity Strict Concurrency CheckingをComplete にしてSwift 6を見据え た対応を進めていく 今後の展望 69

Slide 70

Slide 70 text

想定外のことも往々にして起き続ける 例えば、途中でアプリ名が変わったり… 共通コンポーネントなどはサービスのコードネームにしておいたので、 影響範囲は最小で済みました サービスネームを直で使っている箇所はコードには存在しなかったので、 「アプリアイコン」 「翻訳のマスターとなるスプシのデータ」「Keyが変わった場合その部分の修正」のみ 何があるか分からない 新規開発 70

Slide 71

Slide 71 text

CHECK 大切にしていた心構え 必要な要件と、1stリリースでは やらないことを明確にして進める 意識 完全新規の0→1は初めて。 CHECK 自分でとにかく触って、問題が あったら直してを繰り返す 本当に使いやすいアプリになって いるか。プロダクトの品質を意識 まず作り切るのは大事だが、 良いアプリを作る視点 新規開発 71

Slide 72

Slide 72 text

さいごに 72

Slide 73

Slide 73 text

今回は開発全般について、特に概念的な部分を多く取り上げまし た。4/25ではTCA・KMPのアーキテクチャ構成での開発導入がしや すくなるように、関連する簡易的なサンプルプロジェクトを作成 し、ご紹介する予定です。 iOSアプリのアーキテクチャ設計~TCA実践編~ https://findy.connpass.com/event/315494/ 4/25も登壇するので、そちらもご確認いただけますと嬉しいです! 最後までご清聴いただきありがとうございました!! 73