VPSSpark ブログ
← 開発日記へ戻る

iOS CI ビルド時間短縮:GitHub Actions + Xcode 28 分→9 分

サーバーメモ · 2026.06.06 · 約 10 分

MacBook で iOS CI ビルド短縮結果を確認する開発者
push のたび 28 分——タイムラインを分解して初めて、時間の行き先が見えた。

GitHub Actions + Xcode で回している典型的な iOS プロジェクトでは、iOS CI のビルドが遅いのは「仕方ない」と諦めがちです。しかし本質は iOS CI slow ≠ Xcode slow で、原因は パイプラインのステートレス設計・キャッシュ不在・直列実行 にあります。

本記事では、8 人の iOS チーム(Swift 約 46 万行、Pods 28 個、SPM 14 個、1 日 30 回以上の push)の実例をもとに、GitHub Actions macOS CI を 28 分から 9 分へ短縮した iOS CI optimization の手順をまとめます。

28→9
平均ビルド時間(分)
68%
総短縮率
74%
キャッシュ+並列化の寄与

一、問題の定義:なぜ iOS CI は遅いのか

多くのチームが 20〜30 分の待ち時間を「当たり前」と受け入れています。ボトルネックは Xcode ではなく、CI パイプラインの設計そのものです。

1.1 GitHub Actions CI の構造的な問題

GitHub Actions の macOS runner は ephemeral environment(使い捨て環境) です。ジョブが終わるたびに次が起きます:

  • ❌ DerivedData が消える(incremental build 不可)
  • ❌ CocoaPods を毎回 install し直す
  • ❌ SPM を毎回 resolve し直す
  • ❌ ビルドコンテキストを再利用できない

結果として、毎回 cold build になります。

1.2 ベースラインの時間内訳(28 分)

workflow_dispatch でクリーンビルドを 3 回実行し、中央値で各フェーズを計測しました:

フェーズ所要時間種別
Checkout45s固定
pod install3m 12sネットワーク依存
SPM resolve1m 44s解決処理
xcodebuild build11m 08scold compile
xcodebuild test6m 22sCPU
xcodebuild archive5m 30s直列ブロック
upload/sign1m 05s固定
Total29m 46s

核心ボトルネック:iOS CI が遅い 3 大原因

  1. Cold Build(39%)——DerivedData なしで 46 万行 Swift を毎回フルコンパイル
  2. Dependency Re-resolve(17%)——Pods + SPM を毎回ダウンロード・解決
  3. Serial Pipeline(19%)——test と archive に依存関係がないのに直列実行

二、最適化の目標と結果

最適化結果(iOS CI optimization 概要)

フェーズビルド時間
baseline28 min
+ cache20 min
+ parallel12 min
+ Apple Silicon9 min

最適化効果の内訳

  • Cache:42%(約 8 分短縮)
  • Parallelization:32%(約 6 分短縮)
  • Apple Silicon:26%(約 3 分短縮)

事例の背景(E-E-A-T)

項目
Swift コード量約 46 万行(テスト含む)
CocoaPods / SPM28 Pods + 14 packages
ベースライン RunnerGitHub macos-latest(Intel、4 コア)
チーム / 頻度8 人、1 日 30 回以上の push
計測方法各ステップにタイムスタンプ、冷起動 3 回の中央値

単一変数実験(各構成を 10 回実行し中央値を取得):キャッシュのみ -29%、並列化のみ -21%、Apple Silicon のみ -18%。構造最適化(キャッシュ + 並列化)が 74% を占め、ハードウェアは上限を引き上げる要素であり第一要因ではありません。 p95 は 33 分から 12 分へ、DerivedData キャッシュヒット率は 80%。

三、最適化①:CI キャッシュ(最大の効果)

3.1 キャッシュが鍵である理由

iOS CI optimization の本質は、CI 上で Xcode の incremental build を再び有効にすることです。 DerivedData、CocoaPods cache、Swift Package Manager cache の 3 ディレクトリを永続化する必要があります。

3.2 キャッシュの 3 要素

  • DerivedData~/Library/Developer/Xcode/DerivedData
  • CocoaPods cache~/Library/Caches/CocoaPods
  • SPM cache~/.spm-cache

3.3 GitHub Actions の設定

.github/workflows/ios-ci.yml(DerivedData + CocoaPods + SPM キャッシュ)
- name: Cache DerivedData
                  uses: actions/cache@v4
                  with:
                    path: ~/Library/Developer/Xcode/DerivedData
                    key: deriveddata-${{ runner.os }}-${{ hashFiles('**/Podfile.lock','**/Package.resolved') }}

                - name: Cache CocoaPods
                  uses: actions/cache@v4
                  with:
                    path: ~/Library/Caches/CocoaPods
                    key: pods-${{ runner.os }}-${{ hashFiles('**/Podfile.lock') }}

                - name: Cache SPM
                  uses: actions/cache@v4
                  with:
                    path: ~/.spm-cache
                    key: spm-${{ runner.os }}-${{ hashFiles('**/Package.resolved') }}

3.4 キャッシュ key の設計ポイント

キャッシュヒット率が DerivedData cache の実効性を左右します。key 設計には 3 つの典型的なパターンがあります:

key 戦略ヒット率問題点
runner.os のみ~100%古い Pods を参照し、成果物が不整合になる恐れ
ロックファイルのハッシュ(推奨)~80%依存が変わらなければヒット、更新時のみ再構築
commit SHA を含める~0%push のたびに miss、実質キャッシュなし

Podfile.lock / Package.resolved のハッシュを主 key にし、restore-keys でプレフィックスマッチを設定するのが定石です。主 key が miss しても前回の有効キャッシュを取得でき、完全な cold build を避けられます。CI では pod install --no-repo-update を使い、lock ファイルの書き換えによるキャッシュ無効化を防ぎましょう。

詳細は GitHub Actions cache ドキュメント を参照。macOS runner のキュー待ちについては GitHub Actions macOS runner がいつも並ぶ理由 も合わせて確認してください。

3.5 キャッシュの効果

項目最適化前最適化後
pod install3m 12s50s
SPM resolve1m 44s15s
build11m3–4m

平均 8〜10 分の短縮。

四、最適化②:CI 並列化(Pipeline optimization)

4.1 問題点

デフォルトの CI は build → test → archive と直列です。しかし test と archive には依存関係がなく、並列実行が可能です。

4.2 並列 Job の設計

.github/workflows/ios-ci.yml(test / archive 並列)
jobs:
                  build-and-test:
                    runs-on: macos
                    steps:
                      - run: xcodebuild build test

                  archive:
                    runs-on: macos
                    if: github.ref == 'refs/heads/main'
                    steps:
                      - run: xcodebuild archive

archive は main ブランチのみで起動。各 job は独立してキャッシュを復元します。証明書管理には Fastlane Match を推奨。高頻度 CI のリソース分離については Cloud Mac 3 台で 1 日 500 回の iOS CI を回す方法 も参考になります。

4.3 効果

モード時間
serial20 min
parallel12 min

5〜7 分の短縮。

五、最適化③:Apple Silicon Runner

5.1 なぜ最後にやるのか

ハードウェアは iOS CI slow を解決する第一手段ではありません。 キャッシュなしで Apple Silicon に替えても 18% 程度。構造最適化の後に導入して初めて、12 分から 9 分へ落とせます。

5.2 実測値(キャッシュ + 並列化済み)

フェーズIntelApple Silicon
build3m 40s1m 55s
test6m 22s3m 48s
archive5m 30s3m 10s

5.3 結論

追加で 3〜5 分短縮。 Apple Silicon は Swift のコンパイルとリンクで特に効果が大きく、Apple の incremental build ドキュメント も参照してください。

5.4 3 種類の macOS Runner 比較

構造最適化が終わった段階で、チームは次の 3 つの GitHub Actions self-hosted runner macOS 案から選ぶことが多いです:

方式ビルド時間キュー運用コスト
GitHub macos-latest~20 min高(ピーク時 10〜20 分待ち)ゼロ
自前 Mac mini(Intel)~14 minなし
Cloud Mac(Apple Silicon)~9 minなし

GitHub ホスト runner は無料枠がある一方、キューがボトルネックになりやすく、キャッシュは repo あたり 10 GB 制限で大規模プロジェクトは evict が頻発します。自前 runner はキューを消せますが Intel には天井があります。1 日 30 回以上 push するチームなら、専有 Apple Silicon ノードの ROI が高いケースが多い——構造最適化は自前で、ハードとキューは別途評価するのが現実的です。

六、iOS CI optimization 決定木

iOS CI pipeline optimization checklist
CI > 15 min?
                  ↓
                cold build > 40%?
                  → YES: enable cache (DerivedData + Pods + SPM)
                  ↓
                pipeline serial?
                  → YES: split jobs (test / archive)
                  ↓
                still slow?
                  → upgrade Apple Silicon runner

再現可能な Runbook(5 ステップ):

  1. ベースライン計測:各 step に echo "::notice::$(date -u +%H:%M:%S)" を入れ、冷起動 3 回の中央値を取る
  2. キャッシュ導入:DerivedData + Pods + SPM を設定し、10 回連続でヒット率を計測(目標 > 60%)
  3. workflow 分割:test / archive を独立 job に、archive は main のみ
  4. ハード評価:構造最適化後も > 12 min なら Apple Silicon runner を検討
  5. p95 監視:目標は平均の 1.5 倍以下、ベースライン比 +20% でアラート

七、よくある誤解(iOS CI Optimization Mistakes)

❌ Xcode incremental build in CI works automatically

誤り。CI には DerivedData が残らないため、明示的なキャッシュが必須です。

❌ upgrade Mac solves CI slow

誤り。キャッシュなしでは改善は限定的(約 18%)。

❌ increase -jobs always faster

CPU コア数を超えると逆に遅くなります。M2 では -jobs 6〜8 が目安。

❌ cache key should be exact

commit SHA だと毎回 miss。Podfile.lock / Package.resolved のハッシュを使いましょう。

八、FAQ(GitHub Actions iOS CI よくある質問)

Q1:GitHub Actions の iOS CI が遅いのはなぜ?

GitHub Actions の macOS runner は stateless な ephemeral environment で、DerivedData・Pods・SPM キャッシュを保持しません。ローカルではホットキャッシュがあるのに、CI は毎回ゼロから cold build するため、手元の Xcode build time が速くても CI だけ遅くなります。

Q2:iOS CI optimization で最も効く方法は?

DerivedData + CocoaPods + SPM のキャッシュで、全体の 42%(平均 8〜10 分)を短縮できます。CI 環境における Xcode build time optimization の最優先事項です。

Q3:Apple Silicon だけで CI slow は解決する?

単独では不十分で、あくまで加速の上限です。キャッシュなしでは 18% 程度。キャッシュと並列化と組み合わせて初めて 12 分から 9 分に落とせます。

Q4:Pods と SPM、どちらが遅い?

通常 CocoaPods の方が遅い(3m 12s vs 1m 44s)。ネットワークダウンロードが絡むためです。両方キャッシュすれば効果は加算されます。

Q5:最適化の推奨順序は?

Cache → Parallelization → Apple Silicon。3 段階で 68% 短縮。順番を飛ばすと効果が半減します。

九、まとめ

iOS CI が遅い本質はパフォーマンス問題ではなく:

CI パイプラインが過去の計算結果を一切再利用していない
最適ルート:① Cache(最大効果)→ ② Parallelization(構造改善)→ ③ Apple Silicon(性能上限)。

補足:構造最適化後、専有 Apple Silicon self-hosted runner が必要でハード運用を避けたい場合は、Cloud Mac プラン(キュー待ちゼロ、キャッシュ 10 GB/repo 制限なし)をご検討ください。

期間限定

iOS CI 高速化の最後のピース:Apple Silicon Cloud Mac

専用ランナー · 月額 · ハード保守不要

ホームへ
期間限定 プランを見る