VPSSpark Blog
← Zurück zum Entwicklertagebuch

iOS-CI-Buildzeit: GitHub Actions + Xcode von 28 auf 9 Minuten

Server-Notizen · 2026.06.06 · ca. 10 Min.

Entwickler prüft iOS-CI-Buildzeiten auf dem MacBook
28 Minuten pro Push wirkten normal—bis wir die Timeline zerlegten.

In typischen iOS-Projekten mit GitHub Actions und Xcode gilt lange iOS-CI-Build-Zeit oft fälschlich als Normalzustand. Die Ursache liegt selten in Xcode selbst: iOS CI slow ≠ Xcode slow — entscheidend sind stateless Pipeline, fehlender Cache und serielle Job-Struktur.

Dieser Artikel dokumentiert einen realen Fall aus einem 8-köpfigen iOS-Team (460.000 Zeilen Swift, 28 Pods, 14 SPM-Pakete, 30+ Pushes pro Tag): GitHub Actions macOS CI von 28 auf 9 Minuten — inklusive vollständigem iOS CI optimization-Runbook.

28→9
Ø Build-Zeit (Min.)
68%
Gesamteinsparung
74%
durch Cache + Parallelisierung

1. Problemstellung: Warum dauert iOS CI so lange?

Viele Teams akzeptieren 20–30 Minuten Wartezeit als unvermeidlich. Der Engpass ist nicht Xcode, sondern die Architektur der CI-Pipeline.

1.1 Das Grundproblem bei GitHub Actions CI

GitHub Actions macOS Runner sind eine ephemerale Umgebung (ephemeral environment). Jeder Lauf bedeutet:

  • ❌ DerivedData geht verloren — kein inkrementeller Build
  • ❌ CocoaPods wird bei jedem Lauf neu installiert
  • ❌ SPM resolved bei jedem Lauf neu
  • ❌ Build-Kontext kann nicht wiederverwendet werden

Ergebnis: Jeder Lauf ist ein Cold Build.

1.2 Baseline-Aufschlüsselung (28 Minuten)

Mit workflow_dispatch dreimal kalt gestartet, Median pro Phase:

PhaseDauerTyp
Checkout45sfix
pod install3m 12sNetzwerk
SPM resolve1m 44sAuflösung
xcodebuild build11m 08sCold Compile
xcodebuild test6m 22sCPU
xcodebuild archive5m 30sseriell blockierend
upload/sign1m 05sfix
Gesamt29m 46s

Kern-Engpässe: drei Hauptursachen für langsame iOS CI

  1. Cold Build (39 %) — ohne DerivedData wird bei jedem Lauf die gesamte Swift-Basis (460.000 Zeilen) kompiliert
  2. Dependency Re-resolve (17 %) — Pods und SPM werden jedes Mal neu geladen und aufgelöst
  3. Serial Pipeline (19 %) — Test und Archive laufen nacheinander, obwohl keine Abhängigkeit besteht

2. Ziel und Ergebnis

Optimierungsergebnis (iOS CI optimization im Überblick)

PhaseBuild-Zeit
Baseline28 min
+ Cache20 min
+ Parallel12 min
+ Apple Silicon9 min

Aufschlüsselung der Einsparungen

  • Cache: 42 % (ca. 8 Minuten)
  • Parallelisierung: 32 % (ca. 6 Minuten)
  • Apple Silicon: 26 % (ca. 3 Minuten)

Fallkontext (E-E-A-T)

ParameterWert
Swift-Codebasisca. 460.000 Zeilen (inkl. Tests)
CocoaPods / SPM28 Pods + 14 Packages
Baseline-RunnerGitHub macos-latest (Intel, 4 Kerne)
Team / Frequenz8 Personen, 30+ Pushes/Tag
MessmethodeZeitstempel pro Step, 3× kalt, Median

Einzelvariable-Tests (je Konfiguration 10 Läufe, Median): nur Cache −29 %, nur Parallelisierung −21 %, nur Apple Silicon −18 %. Strukturoptimierung (Cache + Parallel) liefert 74 % — Hardware setzt das Limit, ist aber nicht der erste Hebel. p95 sank von 33 auf 12 Minuten, DerivedData-Trefferquote 80 %.

3. Maßnahme 1: CI-Cache (größter Hebel)

3.1 Warum Cache entscheidend ist

Der Kern von iOS CI optimization: Xcode-Incremental-Builds müssen in CI wieder funktionieren. Dafür müssen DerivedData, CocoaPods-Cache und Swift-Package-Manager-Cache persistent sein.

3.2 Drei Cache-Verzeichnisse

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

3.3 GitHub Actions Konfiguration

.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 Cache-Key-Design

Die Trefferquote bestimmt, ob DerivedData cache wirklich greift. Drei gängige Strategien:

Key-StrategieTrefferquoteProblem
nur runner.os~100 %veraltete Pods möglich, inkonsistente Artefakte
Lockfile-Hash (empfohlen)~80 %Treffer bei unveränderten Dependencies, Rebuild nur bei Update
Commit-SHA enthalten~0 %jeder Push miss — Cache wirkungslos

Empfehlung: Hash von Podfile.lock / Package.resolved als Primary Key plus restore-keys für Präfix-Matching. Bei Primary-Miss bleibt der letzte gültige Cache verfügbar — inkrementeller statt vollständiger Cold Build. In CI unbedingt pod install --no-repo-update, damit Lockfiles nicht versehentlich invalidiert werden.

Details in der GitHub Actions Cache-Dokumentation; Vergleich Remote- vs. lokaler Cache in DerivedData / Pods / sccache im Vergleich.

3.5 Cache-Wirkung

Schrittvorhernachher
pod install3m 12s50s
SPM resolve1m 44s15s
build11m3–4m

Im Schnitt 8–10 Minuten gespart.

4. Maßnahme 2: CI-Parallelisierung (Pipeline optimization)

4.1 Das Problem

Standard-CI: build → test → archive (seriell). Test und Archive hängen nicht voneinander ab — sie können parallel laufen.

4.2 Parallele Jobs

.github/workflows/ios-ci.yml (test / archive parallel)
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 nur auf main; jeder Job stellt den Cache unabhängig wieder her. Zertifikate per Fastlane Match. Ressourcen-Isolation: 3 Cloud Macs für 500 iOS-CI-Läufe pro Tag.

4.3 Wirkung

ModusZeit
seriell20 min
parallel12 min

5–7 Minuten gespart.

5. Maßnahme 3: Apple-Silicon-Runner

5.1 Warum Hardware zuletzt kommt

Hardware ist nicht der erste Hebel gegen langsame iOS CI. Ohne Cache bringt Apple Silicon nur ~18 %; nach Strukturoptimierung sinkt die Zeit von 12 auf 9 Minuten.

5.2 Gemessene Verbesserung (Cache + Parallel aktiv)

PhaseIntelApple Silicon
build3m 40s1m 55s
test6m 22s3m 48s
archive5m 30s3m 10s

5.3 Fazit

Weitere 3–5 Minuten Einsparung. Apple Silicon beschleunigt vor allem Swift-Kompilierung und Linking — siehe Apple-Dokumentation zu inkrementellen Builds.

5.4 Drei macOS-Runner-Optionen im Vergleich

Nach der Strukturoptimierung wählen Teams meist zwischen drei GitHub Actions self-hosted runner macOS-Ansätzen:

OptionBuild-ZeitWarteschlangeWartungsaufwand
GitHub macos-latest~20 minhoch (Spitzen 10–20 min Wartezeit)keiner
Eigener Mac mini (Intel)~14 minkeinehoch
Cloud Mac (Apple Silicon)~9 minkeinegering

GitHub-Hosted-Runner sind kostenlos, aber die macOS-Runner-Warteschlange wird zum Flaschenhals; Cache ist auf 10 GB/Repo begrenzt — große Projekte erleben häufige Evictions. Eigene Hardware eliminiert die Queue, Intel setzt aber ein hartes Limit. Bei 30+ Pushes/Tag lohnt sich ein dedizierter Apple-Silicon-Knoten meist am ehesten: Struktur selbst optimieren, Hardware und Queue separat bewerten.

6. Entscheidungsbaum für 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

Reproduzierbares Runbook (5 Schritte):

  1. Baseline messen: pro Step echo "::notice::$(date -u +%H:%M:%S)", 3× kalt, Median
  2. Cache aktivieren: DerivedData + Pods + SPM, 10 Läufe, Trefferquote > 60 % anstreben
  3. Workflow splitten: test / archive als eigene Jobs, Archive nur auf main
  4. Hardware prüfen: Apple-Silicon-Runner erst, wenn Strukturoptimierung > 12 min hinterlässt
  5. p95 überwachen: Ziel ≤ 1,5× Mittelwert, Alarm bei +20 % über Baseline

7. Typische Fehler (iOS CI Optimization Mistakes)

❌ Xcode incremental build in CI works automatically

Falsch. Ohne DerivedData-Cache gibt es in CI keinen inkrementellen Build.

❌ upgrade Mac solves CI slow

Falsch. Ohne Cache nur ~18 % Gewinn.

❌ increase -jobs always faster

Über der CPU-Kernzahl wird es langsamer. Auf M2 empfehlen wir -jobs 6~8.

❌ cache key should be exact

Commit-SHA erzwingt Cache-Miss. Hash aus Podfile.lock / Package.resolved verwenden.

8. FAQ (häufige Fragen zu GitHub Actions iOS CI)

F1: Warum ist GitHub Actions iOS CI so langsam?

Weil macOS Runner stateless und ephemeral sind — DerivedData, Pods und SPM-Cache gehen nach jedem Lauf verloren. Lokal baut Xcode mit Warm-Cache schnell; in CI startet jeder Lauf bei null.

F2: Was bringt bei iOS CI optimization am meisten?

Cache für DerivedData + CocoaPods + SPM — 42 % Beitrag, 8–10 Minuten im Schnitt. Das ist die erste Priorität für Xcode build time optimization in CI.

F3: Löst Apple Silicon das CI-Problem allein?

Nein — es setzt nur das Performance-Limit. Ohne Cache ~18 %; mit Cache und Parallelisierung von 12 auf 9 Minuten.

F4: Pods oder SPM — was ist langsamer?

CocoaPods meist langsamer (3m 12s vs. 1m 44s) wegen Netzwerk-Downloads. Beide cachen — Effekte addieren sich.

F5: Optimale Reihenfolge?

Cache → Parallelisierung → Apple Silicon. Kombiniert 68 % Einsparung; Reihenfolge nicht überspringen.

9. Fazit

Langsame iOS CI ist kein reines Performance-Problem, sondern:

Die Pipeline recycelt keinerlei frühere Build-Ergebnisse
Optimaler Pfad: ① Cache (größter Hebel) → ② Parallelisierung (Struktur) → ③ Apple Silicon (Obergrenze).

Nach der Strukturoptimierung: Wer einen dedizierten Apple-Silicon-Self-Hosted-Runner ohne Hardware-Betrieb sucht, findet in der Cloud-Mac-Lösung null Wartezeit in der Queue und Cache ohne 10-GB/Repo-Limit.

Limitiert

Schnellere iOS-CI: Apple Silicon Cloud Mac

Dedizierte Runner · monatlich · ohne Hardware-Betrieb

Startseite
Limitiert Tarife ansehen