こんにちは。LIFULLでiOSアプリケーションの開発を担当している山手です。
LIFULL HOME'S iOSアプリは2009年12月にリリースされて以来、約12年ほどサービスを継続しておりプロジェクト規模は年々大きくなっています。
新機能導入や既存機能の改修などを重ねるにつれてアプリのビルド時間が延び、 エンジニアの作業時間が圧迫、開発効率が下ってしまい満足した状態で開発を行うことが難しくなります。 そんな状況を回避すべく、自動化して任せられる部分は任せてエンジニアは開発に専念できる環境を作るために、CI/CDサービスのBitriseを2018年6月頃から導入し約3年ほど利用しています。
今回は、規模の大きくなったLIFULL HOME'S iOSアプリでBitriseを利用する際に直面した問題や事例などをご紹介致します。
Bitriseとは
Bitriseはモバイルアプリ開発に特化したCI/CDプラットフォームを提供してくれるサービスです。 ビルドの一連の流れを「ワークフロー」という形で作成できます。
作成したワークフローに対してトリガーを設定することで、トリガータイミングで自動的にワークフローが走り始めます。 また、これらの作成はGUI上で設定することができ、簡単にCI/CD環境を構築することができます。
HOME'S iOSアプリにおけるBitrise(CI/CD)の役割
- テスト、及びLint Checkの実行
- Pull Requestを立ち上げたタイミング、またはPRにコードをPushしたタイミングでテストとLint Checkを実施し、結果をPRにコメントします。
- テスト用アプリの作成、及び配布
- テスト用アプリを作成し、インストール用リンク(Bitrise.io)のQRコードをSlackに通知します。
- App Store Connectへのデリバリー
- masterブランチからReleaseブランチへPRを出した時に、App Store Connectへのアプリのデリバリーを実施します。
CIを止めるビルド時間
最新の料金プランでは、ビルド時間の上限は設けられていませんが、以前の料金プランでは45分または90分のビルド時間の上限が設けられており、ビルドに時間がかかるプロジェクトの場合はCIが完了する前に強制終了されてしまうことがありました。
画像は以前のBitriseの管理画面のスクリーンショットです。
※一部画像を加工しています
現在は契約プランを見直し上限時間が90分になっていますが当時は45分のプランであったためある時期を境にすべてのPull RequestのUTが通らないという問題が生じました。
主な原因としては、モバイルデータベースのRealmのビルド時間が影響しています。
45分のビルド時間の内、約14分がRealmのビルド時間に専有されてしまいUTが走る前に強制終了されてしまっていたのです。
当時はCocoaPods経由で、Realmを利用していました。CocoaPods経由で利用していた理由は、Realm Framework自体のサイズです。 Carthageを用いればビルド済みのFrameworkをリポジトリに含めることもできるのでビルド時間を短縮することが出来ますが、Realm Frameworkのビルド後に得られるFrameworkのファイルサイズが100MBを超えてしまいGitHubにPush出来ませんでした。 一方CocoaPodsは、都度ビルドしてFrameworkを生成します。リポジトリにはPodfileのみ含めることで、開発者が複数人いてもPod installを実行するだけで利用しているFrameworkを手元に導入することが可能です。 これ以外にもCarthage + Git LFSを用いて、ビルド済みのFrameworkをGit LFS側に置くなどの案もありましたが費用などの問題から当時の開発チームではCocoaPodsでRealmを扱うこととしました。
Realm導入から時が流れ、利用するFrameworkなども増え次第にビルド時間が肥大化し続けたことで、ついに契約プランの上限時間を超えるようになってしまいました。 大きな要因としては、Apple Watch向けの新機能でもRealm Frameworkを利用したことが影響しています。従来iOS向けのビルドだけだったものがwatchOS向けのビルドも必要になったことでビルド時間が大幅に伸びてしまいました。
このままCIが回らない状況が続くとBitriseの月額費用が無駄になってしまうことからiOSチームとしてはCI環境上でのビルド時間短縮が急務でした。 かつ追加のコストを大きく掛けずに問題を解消するということも求められました。
Carthageを活用する
CarthageにはGitHub上にアップロードされているビルド済みのFrameworkを利用する機能が備わっています。 この機能を利用するにはFrameworkをビルドしたXcodeのVersionを揃える必要があります。 今回はこの機能に着目しCI環境上でのビルド時間を短縮してみることにしました。
賛否両論ある問題ですが、今回問題になっているRealm関連のCarthageでのビルド後得られるファイルを.gitignoreでgitの追跡対象から除外します。 Realm以外のCarthage経由で導入したFrameworkは、リポジトリに含めるように変更しています。
# Realmはbinaryを再利用させるため、追跡から除外する */Carthage/Build/iOS/**Realm**.framework/ */Carthage/Build/iOS/**Realm**.framework.dSYM/ */Carthage/Build/iOS/**Realm**.framework/ */Carthage/Build/iOS/**Realm**.framework.dSYM/
この変更によりリポジトリをクローンした直後、Realm以外の必要なFrameworkは得られる環境が出来上がります。 リポジトリクローン後、下記のコマンドを実行しRealmのみ取得します。
$ carthage update --platform "iOS, watchOS" realm-cocoa
実行時、Cartfileで指定したRealm Frameworkのバージョンに紐づくビルド済みのFrameworkをビルドしたXcodeのバージョンがCI環境で利用しているバージョンと一致する場合は、ビルド不要でFrameworkを利用することができます。 これによってCI環境上で約14分ほどかかっていたビルド時間を約2分まで短縮することに成功しました。
導入後のデメリット
ビルド済みのFrameworkをビルドしたXcodeバージョンでないバージョンで、アプリをフルビルドするとFrameworkも再ビルドの必要が生まれてしまいビルド時間が延びてしまいます。 そのため、チーム内で利用するXcodeのバージョンを上げる際にはRealmのバージョンも見直す必要が生まれてしまいました。
通常の機能改修や新機能追加では大きな支障にはならないものの、毎年9月頃に行われるXcodeのメジャーアップデートの際は、Framework側が最新のXcodeに対応することを待たないとCIが利用できないという点が難点です。 このようなデメリットも伴うので、こんな方法あるという事例として頭の片隅に残していただければ幸いです。
デメリットを運用でカバーする
デメリットを許容してしまうとXcodeのメジャーアップデートが無い限りXcodeのバージョンとFrameworkバージョンが固定化されてしまうリスクが生まれてしまいます。 古いバージョンでも開発を継続することは出来ますが、Framework内で利用しているiOSのメソッドの非推奨や廃止などによってFrameworkのバージョンアップが急遽求められてしまう場面などが生まれます。定期的なアップデートを行う体制を整えることで余裕を持った対応も可能になり、新しい機能などの導入も積極的に行うことが出来ます。
現在iOSチームでは、定期的なFrameworkのバージョンアップデートを行うことで、バージョン固定化のデメリット・リスクを低減しようとしています。
Bitriseでライブラリバージョンの更新を監視する
今回は、ライブラリのアップデートを確認しSlackに通知するのワークフローを用意し定期的にワークフローを実行することで、ライブラリバージョンの更新を監視するようにしました。 現在は、CocoaPodsで管理しているライブラリのみを対象にしています。
これを実現するための簡単な手順は以下になります。
- CocoaPods管理下のライブラリの最新バージョンを
pod outdated
を用いて、リストアップする - リストアップした中から、ライブラリとバージョンについて記載されている各行を抽出する
- 抽出した結果をSlackに通知する
実際に作成したワークフローが動作し、届いたSlackへの通知が下記になります。
iOSチームでは、金曜日に次週のタスク設定を行うミーティングがあるので毎週金曜日にpod outdated
の結果を通知しています。
これによりミーティング時にFrameworkの更新状況を確認し、チームメンバー全員でどのFrameworkのアップデートに対して優先的に対応を行うかなどを話し合える機会を作っています。
今後の展開
現在はSlackへの通知に留まっており、アップデート作業などは手作業に頼っています。また、監視対象もCocoaPodsで管理しているFrameworkに留まっています。 今後は、Frameworkのアップデートの必要が生まれれば自動的にPull Requestを自動生成する仕組みやCarthageで管理しているFrameworkも含めて通知できるような仕組みづくりを進めていく予定です。
終わりに
長年開発が続けられてきたプロジェクトのため随所にレガシーな運用であったり、処理が残っています。 日々アプリを利用してくださる方に対して、より良い価値を素早く提供し続けるために、その時その時でチームに合った運用の改善や仕組み作りを行い続けることが大切です。 iOSアプリを通じた住まい探し体験を今以上により良くするためにも、開発のしやすさやアプリの品質向上につながる改善はチームとして継続的に改革し続けていきたいと考えています。
LIFULLでは一緒により良い体験を作っていける仲間を募集しています。よろしければこちらも合わせてご覧ください。