本文へ移動

ホーム / App / 第5部 品質・配信・運用

第5部 品質・配信・運用・第11章

開発環境とビルド

★★☆ 重要8トピック・面接 4問

アプリはビルドが遅く、署名やストアへのアップロードの手順も多いので、開発環境とビルドの仕組みがチームの速さを大きく左右します。ビルドの高速化、Git の運用、CI/CD の道具をまとめます。

ひとことで

計ってから縮める。デバッグは今の CPU だけ・差分のコンパイル・dSYM なし、根本はモジュール分割で作り直す範囲を減らす。

Xcode のビルドが遅いときは、まずどこに時間がかかっているかを計ります。Report Navigator の Build Timeline や、Product メニューの Build With Timing Summary で、ターゲットや段階ごとの時間が見られます。

やること設定・方法効くところ
デバッグは今の端末の CPU だけONLY_ACTIVE_ARCH を Debug で Yesデバッグのビルド
Swift を差分でコンパイルSWIFT_COMPILATION_MODE を Debug は Incremental、Release は Whole Module少しだけ変えたときのビルド
デバッグで dSYM を作らないDEBUG_INFORMATION_FORMAT を Debug は DWARFデバッグのビルド
時間のかかる関数を見つける-Xfrontend -warn-long-function-bodies=200 などで警告を出す型推論が重いコード
モジュールに分けるSwift Package やフレームワークに分ける変更の影響範囲を小さくする

Swift は、型の書いていない長い式(多くの演算子や、型を省いた大きな配列・辞書)の型推論で時間がかかることがあります。警告で見つけたら、型を明示したり式を分けたりします。設定を変えたら、きれいな状態からのビルドと、1ファイルだけ変えたビルドの両方で前後を比べます。

変えたところと、その上だけを作り直すAppFeature A(変更)Feature BFeature CCore(ネットワーク・DB・共通の部品)作り直す:Feature A と AppB・C・Core は前の結果を使う
変えたモジュールと、それに依存するものだけがビルドし直される

面接ではこう答える

Xcode のビルドを速くするには、まず Build Timeline などでどこに時間がかかっているかを計ります。デバッグのビルドでは、今の端末の CPU 向けだけにし、Swift を差分でコンパイルし、dSYM を作らない設定になっているかを確かめます。型推論に時間がかかる関数は警告のオプションで見つけて型を明示します。根本的には、コードを Swift Package などのモジュールに分け、変えたモジュールとその上だけを作り直すようにします。

ひとことで

Build Analyzer で計り、ビルドキャッシュ・構成キャッシュ・並列を有効にし、KSP とモジュール分割で作り直しを減らす。

Android のビルドも、まず計ります。Android Studio の Build Analyzer で時間のかかったタスクやプラグインが見られ、--scan を付けると Build Scan で詳しく調べられます。そのうえで、gradle.properties の設定を確かめます。

2つのキャッシュで、同じ作業をやり直さない構成の段階どのタスクを動かすか実行の段階コンパイル・R8 などできあがりAPK・AAB構成キャッシュビルドキャッシュ入力が同じなら前の結果を使う。並列の実行で、独立したモジュールは同時に作る
設定の段階は構成キャッシュ、各タスクの結果はビルドキャッシュで使い回す
org.gradle.caching=true              # ビルドキャッシュ:タスクの結果を使い回す
org.gradle.configuration-cache=true   # 構成キャッシュ:設定の段階を使い回す
org.gradle.parallel=true              # 独立したモジュールを並列で作る
org.gradle.jvmargs=-Xmx4g            # Gradle のメモリ(端末に合わせて)
ほかに効くこと理由
kapt を KSP に移す注釈の処理(Room・Hilt など)が速くなる。対応しているライブラリから
モジュールに分ける変えたモジュールとその上だけを作り直す。ビルドキャッシュも効きやすい
ビルドのスクリプトで重い処理をしない設定の段階は毎回動くので、ファイルの読み込みや通信を書かない
Gradle と AGP を新しく保つ新しい版ほど速くなる改善が入っている

構成キャッシュは、対応していないプラグインがあると使えないことがあります。有効にしたら警告を見て、プラグインを更新するか置き換えます。

面接ではこう答える

Gradle のビルドは、Android Studio の Build Analyzer や Build Scan で時間のかかるタスクを見つけてから直します。gradle.properties で、タスクの結果を使い回すビルドキャッシュ、設定の段階を使い回す構成キャッシュ、独立したモジュールを同時に作る並列の実行を有効にします。さらに注釈の処理を kapt から KSP に移し、モジュールに分けて変更の影響を小さくします。構成キャッシュは対応していないプラグインがあると使えないので、警告を見て対応します。

11.3CI のキャッシュ

用語★★☆ 重要
ひとことで

依存の一覧のハッシュを鍵にして、前回の取得・ビルドの結果を CI で使い回す。

CI は毎回まっさらな環境で動くので、何もしないと依存のダウンロードやコンパイルを毎回やり直します。特に iOS の macOS のランナーは料金が高めなので、キャッシュの効果が大きくなります。

鍵が同じなら、前回の結果を使うキャッシュの鍵ロックファイルのハッシュ取り出すあれば前回の結果ビルド・テスト保存する鍵が新しいときだけ鍵に入れるもの:Package.resolved・Podfile.lock・Gradle の設定と版のファイル
依存の一覧が変わっていなければ、前回の結果を取り出して使う
キャッシュするもの鍵(変わったら作り直す)メモ
Gradle の依存とビルドキャッシュGradle の設定・版のファイルGitHub Actions なら gradle/actions/setup-gradle が面倒を見てくれる
Swift Package の取得結果Package.resolved取得のやり直しを省く
CocoaPods の PodsPodfile.lock使っている場合
Ruby の gem(fastlane)Gemfile.lockfastlane の準備を速くする

Xcode の DerivedData(ビルドの途中の結果)をまるごとキャッシュする方法もありますが、大きく、壊れた状態を引き継ぐこともあるので、効果を計ってから使います。キャッシュが効いているかは、ログのヒットの表示と、ジョブの時間の推移で確かめます。

CI とビルドの自動化(テスト・署名)

面接ではこう答える

CI は毎回まっさらな環境で動くので、依存の取得やビルドの結果をキャッシュして使い回します。鍵には Package.resolved や Gradle の設定ファイルのように、変わったら作り直すべきもののハッシュを使います。Gradle は setup-gradle のような専用の仕組みに任せ、Xcode の途中の結果をまるごと保存するかは効果を計ってから決めます。ヒットしているかをログで確かめます。

ひとことで

main にこまめにマージし、リリースの日に release ブランチを切って凍結する。出した版にはタグを付け、修正は main に戻す。

アプリは審査とテストに時間がかかるので、リリースする版のコードを、開発の続きから切り離す仕組みが要ります。多く使われるのは、main に短いブランチからこまめにマージし、リリースの日に release ブランチを切る形です。

ブランチ・印役割
main普段の開発。未完成の機能は Feature Flag で隠してマージする
機能のブランチ短く保つ(数日まで)。長く分かれると衝突が増える
release/1.5 などリリースの日に main から切る。ここでは不具合の修正だけ
タグ v1.5.0ストアに出したコミットの印。後から原因を追うときの手がかり
緊急の修正(hotfix)出した版のタグや release ブランチから直し、1.5.1 として出す

大事なのは、release ブランチで直したものをmain にも必ず戻すことです。戻し忘れると、次の版で同じ不具合が復活します。修正はまず main に入れてから release ブランチに取り込む(cherry-pick)順にするチームもあります。

main から切って、直したら戻すmainrelease/1.5修正タグ v1.5.0コード凍結の後は release ブランチで直す。直したものは main にも入れる
リリースの日に main から切り、直したものは main に戻す。出した版にはタグ

面接ではこう答える

アプリ開発では、普段は main に短い機能のブランチからマージし、未完成の機能は Feature Flag で隠します。リリースの日に main から release ブランチを切ってコードを凍結し、テストと審査の間の修正だけをそこに入れます。ストアに出したコミットにはタグを付け、緊急の修正はそこから作ります。release ブランチで直したものは main にも戻し、次の版で不具合が復活しないようにします。

見せる版(1.5.0)と、ストアが新旧を見分けるビルド番号は、人が手で上げずに決まりで付けると間違いが減ります。

番号iOSAndroid付け方の例
見せる版MARKETING_VERSIONversionNamerelease ブランチを切るときに決める。1.5.0 → 1.6.0
ビルド番号CURRENT_PROJECT_VERSIONversionCodeCI の実行番号や、コミットの数から自動で付ける
気をつけること理由
ビルド番号は上げるだけAndroid の versionCode は前より大きくないと出せない。iOS も同じ版の中でビルド番号が重なるとアップロードできない
iOS と Android で番号を分けて持つ片方だけの修正版を出すことがある
番号とコミットを結びつけるタグやビルドのログで、どのコミットのビルドかをたどれるようにする

Android の versionCode は整数で上限があるので、版から計算する方式(1.5.0 → 10500 など)にするなら、桁が足りるかを先に決めておきます。

版の番号とリリースの周期

ひとことで

project.pbxproj は衝突しやすいので、Swift Package に移す・フォルダーで取り込む・XcodeGen や Tuist で生成する、で載るものを減らす。

Xcode のプロジェクトファイル(project.pbxproj)は、ファイルを足す・消す・移すたびに変わる、人が読みにくいファイルです。複数人で開発すると衝突しやすく、解決を誤るとプロジェクトが開けなくなります。

対策効果・注意点
コードを Swift Package のモジュールに移すパッケージの中のファイルはプロジェクトファイルに載らないので、ファイルを足しても衝突しない
Xcode 16 以降のフォルダー(自動で中身を取り込むグループ)フォルダーにファイルを置くだけで取り込まれ、プロジェクトファイルの変更が減る
XcodeGen・Tuist でプロジェクトを生成する短い設定ファイルだけを管理し、プロジェクトファイルはリポジトリに入れない。道具の学習と更新の手間はかかる
ファイルの追加をまとめて行う同じ時期に多くの人がファイルを足すと衝突しやすい
union のマージで済ませない.gitattributes で union を指定する方法は、重複や壊れた項目が残ることがあるので勧めない

Android の Gradle の設定は普通のテキストなので衝突は比較的少ないですが、ライブラリの版はバージョンカタログ(libs.versions.toml)にまとめておくと、変更の場所が1か所になります。

衝突しやすいファイルを、作るものにする設定ファイルproject.yml など(短い)XcodeGen・Tuist生成する道具.xcodeprojリポジトリに入れないほかの道:コードを Swift Package に移す、Xcode 16 以降のフォルダーをそのまま取り込む形式を使う
設定ファイルだけをリポジトリに置き、プロジェクトファイルは各自・CI で作る

面接ではこう答える

Xcode のプロジェクトファイルはファイルを足すたびに変わるので、複数人の開発で衝突しやすく、解決を誤ると開けなくなります。対策は、プロジェクトファイルに載るものを減らすことで、コードを Swift Package のモジュールに移したり、Xcode 16 以降のフォルダーを自動で取り込む形式を使ったりします。さらに XcodeGen や Tuist で設定ファイルから生成し、プロジェクトファイル自体をリポジトリに入れない運用もあります。union のマージで済ませるのは壊れることがあるので避けます。

ひとことで

証明書の秘密鍵・アップロード鍵・API のキーはリポジトリに入れず、CI のシークレットや暗号化した保管場所から使うときだけ取り出す。

アプリのリポジトリには、署名やストアへのアップロードに使う大事なファイルが紛れ込みやすいです。リポジトリに入れたものは、消しても履歴に残るので、最初から入れないようにします。

鍵はリポジトリの外。使うときだけ取り出す保管場所CI のシークレット暗号化した保管庫CI のジョブ取り出して一時的に登録署名したアプリストアへリポジトリ:入れない入れてしまったら、履歴から消すだけでなく鍵を作り直す
鍵はリポジトリの外に置き、CI がビルドのときだけ取り出して使い、終わったら消す
もの置き場所
iOS の証明書の秘密鍵(.p12)・プロファイルfastlane の match などで暗号化して1か所に。または Xcode Cloud などの自動署名
App Store Connect API のキー(.p8)CI のシークレット
Android のアップロード鍵(.jks・.keystore)とパスワードCI のシークレット(ファイルは base64 にして保存するなど)
Google Play のサービスアカウントのキーCI のシークレット。権限は必要なものだけ
外部サービスの秘密のキーアプリに入れずサーバーへ

.gitignore に鍵のファイルの拡張子を入れ、プッシュ前に秘密を見つける仕組み(GitHub のシークレットスキャンなど)も使います。Google Play のアプリ署名を使っていれば、アップロード鍵をなくしたり漏らしたりしても、再設定を頼めます。

API キーと秘密をアプリに入れない

面接ではこう答える

署名の証明書の秘密鍵、Android のアップロード鍵、App Store Connect API のキーなどは、リポジトリに入れません。入れると消しても履歴に残るからです。iOS は fastlane の match のように暗号化して1か所で管理するか、Xcode Cloud の自動署名を使い、Android の鍵やキーは CI のシークレットに置いてビルドのときだけ取り出します。もし入れてしまったら、鍵を作り直して古いものを無効にします。

ひとことで

手順は fastlane の lane にまとめ、GitHub Actions や Xcode Cloud などの CI から動かす。

アプリの CI/CD は、どこで動かすか(CI のサービス)と、何を動かすか(ビルド・署名・アップロードの手順)に分けて考えると選びやすくなります。

1つの命令で、テストからストアまでテストscan署名の準備matchビルドgymアップロードpilot・supply通知Slack など同じ lane を手元でも CI でも動かせるので、「CI でだけ失敗する」を調べやすい
テスト・ビルド・署名・アップロードを1つの命令にまとめ、CI から呼ぶ
道具種類特徴
fastlane手順を書く道具テスト(scan)・署名(match)・ビルド(gym)・TestFlight へのアップロード(pilot)・Google Play へのアップロード(supply)などを lane としてまとめる。どの CI でも動く
Xcode CloudApple の CI/CDApp Store Connect と一体で、署名や TestFlight への配信を設定だけで行える。iOS・Mac のアプリ向け
GitHub Actions汎用の CIリポジトリと一体。macOS のランナーで iOS もビルドできる。Android は Linux のランナーで安く動く
Bitrise・Codemagic などモバイル向けの CIアプリのビルドに必要な手順が部品として用意されている

よくある組み合わせは、GitHub Actions などの CI から fastlane の lane を呼ぶ形です。手順を fastlane に書いておけば、CI のサービスを変えても中身を書き直さずに済みます。どれを使っても、署名の鍵の扱い、キャッシュ、失敗したときの通知を最初に決めておきます。

CI とビルドの自動化

面接ではこう答える

アプリの CI/CD は、どこで動かすかと何を動かすかに分けて考えます。何を動かすかは fastlane が定番で、テスト、署名の準備、ビルド、TestFlight や Google Play へのアップロードを lane としてまとめ、手元でも CI でも同じように動かせます。どこで動かすかは、リポジトリと一体の GitHub Actions、App Store Connect と一体で署名や配信まで設定で済む Xcode Cloud、モバイル向けのサービスなどから選びます。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • ビルド時間は計ってから縮める(Xcode・Gradle の設定、モジュール分割、CI のキャッシュ)
  • リリースブランチと番号の付け方を決め、プロジェクトファイルの衝突を減らす
  • 署名の鍵と秘密はリポジトリに入れず、CI/CD で自動化する

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1アプリのビルドが遅いと言われたら、どう改善しますか?

模範回答

まず計ります。Xcode なら Build Timeline や Build With Timing Summary、Android なら Build Analyzer や Build Scan で、どのモジュールやどの段階に時間がかかっているかを見ます。推測で設定をいじると、効かないうえに壊すことがあるからです。

そのうえで、デバッグのビルドでは今の端末の CPU 向けだけにする、Swift は差分のコンパイルにする、Gradle はビルドキャッシュと構成キャッシュと並列を有効にする、kapt を KSP に移す、といった設定を確かめます。根本的には、よく変わるコードをモジュールに分けて、変更の影響を小さくします。CI ではキャッシュを効かせ、改善の前後の時間を記録して効果を確かめます。

深掘りされたら

  • 型推論に時間がかかっている Swift の関数は、どう見つけますか?
  • CI のキャッシュが効かないとき、何を疑いますか?

見られているポイント計測から始める・設定とモジュール分割・CI まで含める

Q2アプリ開発でのブランチの運用を説明してください。

模範回答

普段の開発は main に短い期間のブランチからマージし、未完成の機能は Feature Flag で隠します。リリースの日になったら main から release ブランチを切ってコードを凍結し、テストと審査の間に見つかった不具合はそのブランチで直して、main にも戻します。

ストアに出した版にはタグを付け、出した後に緊急の修正が必要なら、そのタグか release ブランチから修正版を作ります。アプリは審査があり古い版も残るので、「どのコミットがどの版としてストアに出たか」をいつでもたどれることが大事です。

深掘りされたら

  • release ブランチでの修正を main に戻し忘れないためには?

見られているポイントリリースブランチ・タグ・修正の戻し方

Q3Xcode のプロジェクトファイルのコンフリクトが多いとき、どうしますか?

模範回答

.pbxproj はファイルを足すたびに変わるので、複数人で開発すると衝突しやすいです。対策は、まずプロジェクトファイルに載るものを減らすことで、コードを Swift Package のモジュールに移すと、ファイルの追加でプロジェクトファイルが変わらなくなります。Xcode 16 以降なら、フォルダーの中身を自動で取り込む形式のグループを使うのも効きます。

それでも多いなら、XcodeGen や Tuist のように、設定ファイルからプロジェクトを作る道具を使い、プロジェクトファイル自体はリポジトリに入れない運用にします。衝突したときに union のマージで済ませると、壊れたプロジェクトになることがあるので勧めません。

深掘りされたら

  • プロジェクトを生成する道具を入れるときの注意点は?

見られているポイント衝突の原因を減らす根本対策

Q4署名の証明書や鍵を、チームと CI でどう管理しますか?

模範回答

証明書の秘密鍵、プロビジョニングプロファイル、Android のアップロード鍵はリポジトリにそのまま入れません。iOS は fastlane の match のように暗号化して1か所で管理し、チームと CI が同じものを取り出す形にするか、Xcode Cloud のように Apple 側で署名を管理する仕組みを使います。App Store Connect API のキーで自動化すると、人の Apple ID に頼らずに済みます。

Android はアップロード鍵を CI のシークレットに置き、ビルドのときだけ取り出します。Google Play のアプリ署名を使っていれば、アップロード鍵をなくしても再発行を頼めます。万一リポジトリに入れてしまったら、履歴から消すだけでなく、鍵を作り直して古いものを無効にします。

深掘りされたら

  • 退職した人の Apple ID で作った証明書が期限切れになったら?

見られているポイント秘密を置かない・1か所で管理・漏れたときの作り直し