CA.swiftは、サイバーエージェントのABEMAやAmeba、AWA、tappleなどを担当しているiOSエンジニアによるiOSエンジニアのための勉強会です。 それぞれのチームで使用している技術や開発体制など、開発の中でのノウハウを惜しみなく発信していきます。
第22回のテーマは 「Swiftの進化を活かした技術基盤への挑戦」 です。サイバーエージェントでは、Swift ConcurrencyやSwiftUI、TCAなど次世代技術を活用しながら、いかにして現場の技術基盤を進化させてきたのか。そのプロセスで生まれた試行錯誤や、技術選定のポイント、実例に基づく工夫の数々を共有する予定です。
本記事は、2025年01月08(水)に開催した「CA.swift #22」において発表された「大規模プロジェクトにおける段階的な技術刷新について」に対して、社内の生成AI議事録ツール「コエログ」を活用して書き起こし、登壇者本人が監修役として加筆修正しました。
安井 瑛男 | 株式会社AbemaTV
GitHub : akkyie X : @akkyie
2019年に株式会社AbemaTVへ新卒入社してから、iOSアプリの開発基盤改善や、KMPを活用するためのマルチプラットフォームなアーキテクチャの設計などに携わってきました。趣味は寝ることです。
本日は「大規模プロジェクトにおける段階的な技術刷新」というテーマでお話をさせていただきます。株式会社AbemaTVの安井です。よろしくお願いいたします。
株式会社AbemaTVに2019年に新卒入社し、以来ABEMAのプロジェクトに携わっています。主にiOSアプリの基盤領域を担当しており、2021年頃からはアプリのアーキテクチャ設計に関する業務に注力しています。これまでにも技術に関する発信を何度か行っていますので、もしよろしければそちらもご覧いただければと思います。
本日は、ABEMAチームの背景から、現在進行中の移行プロジェクトについて、また技術スタックやキャッチアップのための取り組みについてご紹介させていただきます。
ABEMAは間もなく開局10周年を迎えようとしています。開局以来、サービスは着実に進化を続けてきました。しかし、急速な成長を遂げる一方で、技術的な負債も蓄積されてきた状況にありました。
CocoaPods によるライブラリ管理を行い、RxSwift を全般的に活用していました。また、UI の実装に Storyboard を使用している部分もありました。これらを新しいトレンドに追従していきたいと考えていました。
中でも特に大きなトピックとして、Kotlin Multiplatform (KMP) の採用があります。ABEMAでは2021年頃から段階的に導入を開始し、検証を重ねてきました。その結果、実運用に耐えうると判断し、本格的な導入へと移行しました。
現在では、iOSエンジニアを含むほとんどのモバイルアプリ開発者が、Kotlin Multiplatformを利用した共通実装部分についての開発にも参加している状況です。
Kotlin Multiplatformを本格的に導入にあたっては、Swiftによるネイティブ実装が主体となっている現状から、ビジネスロジックと呼ばれるような共通ロジックをKotlin Multiplatform側で実装し、UIの実装やプラットフォーム固有の一部の実装のみをSwiftで実装する形を目指すことになります。
このような移行は、実質的にアプリの大部分を再実装することに相当する大規模な取り組みとなります。そのため、どのように段階的に進めていくかが重要な課題となりました。
そもそも負債化した依存が残っていたり、古い設計や実装が残っていたりする中で、それらを維持しながら段階的に移行していくというアプローチは、コストが高くなりすぎてしまう懸念がありました。
そこで、ABEMAでは新しいプロジェクトを作成して、段階的に移行していくという形を取りました。
今回はその移行プロジェクトの進め方について説明したいと思います。
これまでのプロジェクト構成として、2つのリポジトリが存在していました。一つは、iOSアプリ用のリポジトリで、Swiftの既存実装が含まれており、CocoaPodsを使用して依存ライブラリを管理しています。もう一つは、Kotlin Multiplatformの共通実装を格納するためのリポジトリです。
Kotlin Multiplatformで実装したコンポーネントは、フレームワークとしてビルドした形で既存の実装から参照されていました。
今回の移行プロジェクトでは、まずKMPの共通実装が入っていたリポジトリの方に、新しくXcodeプロジェクトを作成しました。
この新しいプロジェクトでは、依存のライブラリの管理はSwift Package Managerで行い、UI実装にはSwiftUIを用いるなど、新しい技術スタックを積極的に使っていくという形にしました。設計としても、大半がKotlin Multiplatformで共通化されているという前提のものになっています。
この新プロジェクトでは、既存の古い実装は全く使えない状態になっており、必要な場合は新しいプロジェクトで再実装することを強制することで、必要なものから徐々に移行されていく状態を作っています。
一方で、既存の実装からは新しいプロジェクトの実装を利用できる上に、新しいプロジェクトのほうで依存しているSwift Package Managerのパッケージも使用できるようになっています。これにより、もともとCocoaPodsを使用していたライブラリなどを、Swift Package Managerに移行していくことができるようになっています。
既存のプロジェクトは、画面単位で新しいプロジェクトの実装を利用する形にしています。新しいプロジェクト側では、UIViewControllerやSwiftUIのViewなどの単位で、既存のプロジェクトにインターフェースとして使えるものを公開しています。これにより、既存の実装にあるルーティングの実装の中で、これらの新しい実装を利用して表示することができます。
このアプローチにより、新しいプロジェクトで再実装された画面を既存のアプリに取り込んでリリースする際に、どちらの実装を表示するかをFeature Flagによって切り替えることもできます。実装の段階的な適用範囲の拡大や、新しい実装で不具合が見つかった場合の切り戻しも容易になります。いわゆる「ストラングラーフィグ・パターン」と呼ばれる手法が実践できたと考えています。
実際にこの移行プロジェクトはどういったステータスにあるのかというと、ABEMAには大きく分けて3種類の作品種別があり、そのうちの1種類を視聴する画面がすでに移行された状態になっています。これにより、動画の再生や、画面回転やモード切り替えといった複雑なインタラクションなど、多くの機能を含んだ主要な画面の一つが移行できたため、進め方としてうまくいきそうだというところが実証できたという状態になっています。
他2種類の移行も現在進行中です。新規にアプリに実装される機能については、積極的に新プロジェクトで行っていこうという方針となっています。また、画面に限らず、アプリを起動したときのスプラッシュ画面の表示といったシーケンス・ロジック部分も移行していこうという形で計画中です。
続いて、その新しいプロジェクトの方で使っている技術スタックをもう少しご紹介したいと思います。
まず、依存管理にはSwift Package Managerを使用しています。これは、現状外部のサードパーティのライブラリの管理のみに使用しており、アプリ内でマルチモジュールのような形で実装を分割する用途には使っていません。
また、SwiftUIも積極的に採用しています。ただし、既存のルーティングとの連携も考慮して、ルーティングはUIKitベースで統一しています。画面単位でのUI実装や状態管理にはSwiftUIをベースにしており、これらの実装にはいくつかの工夫が必要でした。その工夫のひとつについては廣川さんのセッションで説明しています。
さらに、Swift Concurrencyの対応についても、Strict Concurrency Checkingのオプションを有効化し、運用しています。
Swift Package Managerについては、実は当初、アプリ内のモジュール定義にも使用していました。しかし、最終的には既存プロジェクトと同じように、XcodeGenを使用したXcodeプロジェクトとしての定義に戻しました。これはKotlin Multiplatformとの相性に問題があったためです。Kotlin Multiplatformでは、アプリのビルドの度にKotlin側のビルドも実行して実装を更新できるのが理想ですが、Swift Package Manager単体の機能でそれを実現するには多くの制約がありました。
代替手段としてXcodeのスキーマに設定するPre-build ActionでXCFrameworkを生成して参照する方法も採れますが、この方法ではアプリ全体のビルドが不安定になってしまう問題がありました。この問題は新しいプロジェクトだけでなく、取り込んだ既存プロジェクトでも発生したため、Swift Package Managerによるモジュール定義は諦めることとなりました。
Swift Concurrencyについては、既存プロジェクトと新プロジェクトでの対応方針を分けています。まず、既存プロジェクトではSwift 5モードで開発しており、Concurrency関連の機能の導入は積極的に行わないことにしています。これは、RxSwiftなどフレームワークを実装の全体で利用している状態でConcurrencyも含めた実装を行うのは難易度が高い上に、結果的にコードが複雑化したり実装が不安定になったりするリスクがあると考えたためです。これらが移行による恩恵に見合わないと判断し、既存プロジェクトの中で移行するよりも、実装自体を新プロジェクトに移行することを優先しています。
次に新しいプロジェクトのほうでは、結論から述べると、Swift 6モードには変更できていない状態です。これはKotlin MultiPlatform、正確に言えばObjective-Cとの互換性の問題が原因で、Objective-Cのフレームワークに渡されたクロージャーが呼び出されたとき、Swift 6モードではSwiftランタイムがクラッシュする場合があるという問題が発生していたためです。[^1]
このため、現在はSwift 5モードに戻しつつ、Swift 6にすると自動的に有効になるFeatureをすべて有効にして運用しています。
[^1]: こちらの登壇の後、この問題を解決するためのSwift Evolution ProposalがAcceptされました。
Swift Concurrency については、運用ポリシーを社内のドキュメントとして明文化しています。具体的には、準拠できるデータ型は積極的に Sendable に準拠させること、UI 関連の型や処理は積極的に@MainActor 属性を付与することなどを決めています。
さらに、まだ Swift ConcurrencyやSwift 6 に対応していないライブラリを使用する際にコンパイルエラーが発生することがよくあるため、そのような場合にどう対応するべきかという方針もドキュメントとしてまとめています。
Concurrencyを利用する上で工夫している点のひとつが、TaskBagという型の導入です。これは、もともとABEMAで利用しているRxSwiftのDisposeBagと同じような形でタスクを管理できるものです。
DisposeBagと同じようにタスクを追加しておくことで、そのTaskBagを保持しているインスタンスが解放されたときに、自動的にタスクもキャンセルされるようになっています。
TaskBagにタスクを追加するインターフェースでは、Taskのイニシャライザーの @_implicitSelfCapture という属性をあえて外しています。これは、Task自体はが self セルフをキャプチャしてしまっても問題ないものですが、deinitでキャンセルしたい場合、selfを参照し続けていると解放されない場合があるため、あえてselfのキャプチャを明示的に行うことを必須にしています。
その他、キャッチアップのための取り組みをいくつか紹介したいと思います。
Concurrency や Swift 6 関連の話題は、本日登壇したメンバーも含めて、ABEMA、タップル、Ameba、CL などを中心としたサイバーエージェント全体として、どのように対応しているか、どのような課題があったかなどの知見を共有する時間をつくりました。
また、Concurrencyに関する基礎知識を提供するためのXcode Playgroundを作成し、社内で共有しています。
Xcode Playgroundを利用したことで、たとえば、Actorではない通常のクラスで実装すると問題が起こるようなところでActorを使うと修正できる、といった違いを実際に動作させることで確認できるようになっています。
最後に、今後どういった取り組みをしていきたいと考えているかご紹介します。
まずは移行プロジェクトについて、まだ一つの画面が移行し終わった段階で始まったばかりなので、まずは既存プロジェクトの実装を新プロジェクトに移していくところを頑張りたいと思っています。
あわせて、現在はiOSのリポジトリとKotlin Multiplatformのリポジトリが分かれていますが、最終的には一つのリポジトリにすべて入っていた方が開発効率は上がると考えており、将来的にはAndroidやtvOSのアプリも含め、一つのリポジトリに各プラットフォームの実装が入っているような形を目指すことも視野に検討しています。
また、運用ポリシーやキャッチアップ資料も充実させていきたいですし、共通知識についての資料も整っていけば、ゆくゆくは社外発信もできたらと考えています。
最後にまとめとなります。
ABEMAでは、KMP中心の実装に移行すると同時に、技術スタックの刷新も進めています。新規プロジェクトを立ち上げて画面単位で取り込むことで、技術的負債を解消しながら段階的に移行しています。Concurrencyなどの新しい技術スタックについては、運用ポリシーおよびキャッチアップ資料を整備して導入を始めています。また、長期的にはモノリポジトリ化も検討しています。
以上になります。ありがとうございました。