Quand la CI iOS sur GitHub Actions semble lente, le coupable n'est rarement la CPU — c'est le cache absent ou des clés mal choisies, à trois endroits :
- CocoaPods : pas de cache
Pods/→ chaque run refaitpod install - Swift Package Manager (SPM) : graphe de dépendances re-résolu ;
.buildeffacé à chaque job - Xcode DerivedData : cold builds → compilation Swift complète et rebuild des modules
Bien câbler le cache CocoaPods sur GitHub Actions, le cache SPM et le cache DerivedData, et le warm build chute souvent de 30 % à 60 % (comparaison Shadow dans la cascade waterfall).
1. Pourquoi la CI iOS GitHub Actions traîne
La première question d'équipe : « macos-latest est sous-dimensionné ? » « On passe au M4 ou à plus gros ? »
Dans les vrais cas de ralentissement Xcode CI, le goulot n'est rarement la CPU. Ce sont trois coûts cachés :
- Re-résolution des dépendances (CocoaPods / SPM)
- Cold starts DerivedData (recompilation complète)
- Misses actions/cache — chaque run ressemble à un premier build
GitHub Actions amplifie le phénomène : runners éphémères (disque propre à chaque job), le cache ne « colle » pas tout seul, et les setups multi-branches / multi-jobs polluent les clés. Si vous cherchez pourquoi le même commit compile vite un run et lentement le suivant, séparez d'abord cold vs warm, puis ajustez le cache avec ce guide terrain.
2. Où part vraiment le temps en CI iOS
2.1 CocoaPods (cache Pods)
Coût cold typique : pod install environ 3 à 8 minutes.
Cause : chaque run CI relance pod install — resolve, download, integrate depuis zéro.
Correctif : cacher Pods/ avec actions/cache ; clé = hash Podfile.lock + github.ref + arm64. En CI, pod install --deployment.
2.2 Swift Package Manager (SPM)
Coût typique : résolution du graphe 1 à 5 minutes.
Cause : re-résolution à chaque run ; .build effacé ; Package.resolved absent de la clé cache.
Correctif : cacher ~/Library/Caches/org.swift.swiftpm et .build ; clé incluant le hash Package.resolved (voir §3.2).
2.3 Xcode DerivedData
Surcoût cold typique : compilation complète ou quasi 5 à 15 minutes.
Cause : DerivedData non réutilisé → compile Swift complète, retraitement assets, rebuild modules.
Correctif : xcodebuild -derivedDataPath DerivedData et cacher ce chemin (voir §3.3).
3. Une configuration cache CI iOS GitHub Actions qui tient la route
Pour runs-on: macos-latest. Placez les trois blocs dans le même job, avant pod install / xcodebuild (actions/cache@v4 sauvegarde automatiquement).
3.1 Cache CocoaPods (obligatoire)
- name: Restore Pods cache
uses: actions/cache@v4
with:
path: Pods
key: pods-arm64-${{ github.ref }}-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
pods-arm64-${{ github.ref }}-
- name: Pod install
run: pod install --deployment
3.2 Cache SPM (obligatoire)
- name: Restore SPM cache
uses: actions/cache@v4
with:
path: |
~/Library/Caches/org.swift.swiftpm
.build
key: spm-arm64-${{ github.ref }}-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
spm-arm64-${{ github.ref }}-
3.3 Cache DerivedData (fort impact)
- name: Restore DerivedData cache
uses: actions/cache@v4
with:
path: DerivedData
key: dd-arm64-${{ github.ref }}-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
dd-arm64-${{ github.ref }}-
- name: Build iOS
run: xcodebuild -scheme MyApp -derivedDataPath DerivedData ...
Attendez Cache hit occurred dans les logs avant de comparer les durées d'étapes. En cas de miss, corrigez d'abord clé et chemin — ne blâmez pas encore la version Xcode.
4. Design des clés cache (ne pas zapper)
Beaucoup d'efforts d'optimisation CI iOS échouent sur les clés, pas sur les runners.
| ✔ À inclure | ❌ Erreurs courantes |
|---|---|
Architecture (arm64) |
Pas de split arm64/x86 — changez de runner, tout miss |
Branche (github.ref) |
Lockfile seul — DerivedData se mélange entre branches |
| Hash lockfile | restore-keys trop large (ex. préfixe dd-) → mauvais hit |
Quand le lockfile change, un préfixe comme dd-arm64-${{ github.ref }}- peut réutiliser partiellement le cache. Multi-schemes ? Ajoutez ${{ matrix.scheme }} à la clé. Jobs concurrents sur un même runner ne doivent pas partager un seul root DerivedData — isolez avec des sous-dossiers ${{ github.run_id }}.
5. Self-hosted / Cloud Mac (avancé)
Sur Mac mini ou Cloud Mac en runner self-hosted, vous n'êtes pas limité à actions/cache — le disque persiste et les warm builds sont plus stables :
- Chemin DerivedData fixe
- Répertoire Pods fixe
- Cache warm sur disque (économise 30–90 s d'upload/download par job)
export DERIVED_DATA=/Volumes/Data/DerivedData/App-${{ github.run_id }}
export PODS_ROOT=/Volumes/Data/Pods/${{ github.ref_name }}
Exemple de job complet et disque données 1 To : workflow ci-dessous et FAQ branchement CI/CD.
env:
DERIVED_DATA: /Volumes/Data/DerivedData/App-${{ github.run_id }}
PODS_ROOT: /Volumes/Data/Pods/${{ github.ref_name }}
jobs:
build:
runs-on: [self-hosted, macos-m4-ios]
steps:
- uses: actions/checkout@v4
- name: Prepare dirs
run: mkdir -p "$DERIVED_DATA" "$PODS_ROOT"
- name: Pod install
run: pod install --deployment
- name: xcodebuild
run: xcodebuild -scheme MyApp -derivedDataPath "$DERIVED_DATA" build
6. Cache miss ≠ runner lent (erreur fréquente)
Les équipes attribuent souvent une CI iOS GitHub Actions lente au runner ou à la version Xcode. Plus souvent :
| Ce que vous voyez | Cause plus probable |
|---|---|
| Build soudainement plus lent | Cache miss |
| P95 instable | Cold builds mélangés aux stats warm |
| Ralentissements aléatoires | Contention DerivedData entre jobs parallèles |
| Dépendances reconstruites from scratch | Mauvaise clé cache |
À croiser avec le guide variance de build : séparer cold/warm → tuner le cache → puis évaluer le matériel (2026 : pourquoi la CI/CD iOS tourne sur Mac mini M4).
7. Gains mesurés
Correctifs cache seuls, économies typiques :
| Couche | Temps en plus sur cold miss | Après cache câblé |
|---|---|---|
| CocoaPods | 3–8 min | warm souvent <30 s–3 min |
| SPM | 1–5 min | resolve bien plus court sur hit |
| DerivedData | 5–15 min | warm surtout compile incrémentale |
Sur notre run Shadow 14 jours, l'optimisation cache seule a réduit le wall clock warm d'environ −1:40. En éliminant la file et en passant self-hosted avec disque fixe, warm P95 14:12 → 6:05 (−57 %) — voir la vue d'ensemble optimisation.
8. FAQ
Pourquoi la CI iOS GitHub Actions est-elle si lente ?
Pas la CPU en général — CocoaPods / SPM / DerivedData sans cache ou mal clés. Les runners éphémères amplifient chaque miss.
Comment cacher CocoaPods sur GitHub Actions ?
Cacher Pods/ ; clé : pods-arm64-${{ github.ref }}-${{ hashFiles('**/Podfile.lock') }}. Voir §3.1.
Comment cacher SPM en CI iOS ?
Cacher le répertoire swiftpm global et .build ; lier la clé à Package.resolved. Voir §3.2.
Quel chemin pour le cache DerivedData ?
Le path de actions/cache doit correspondre à xcodebuild -derivedDataPath ; le défaut système n'est pas obligatoire.
Cache trop volumineux ?
GitHub plafonne chaque entrée à environ 10 Go. Scindez les clés DerivedData par scheme, ou self-hosted avec disque fixe sans upload.
macos-latest vs self-hosted ?
Hosted : seulement actions/cache, peut vider entre jobs. Self-hosted : /Volumes/Data fixe, warm builds plus stables. Comparaison dans la vue d'ensemble optimisation.
9. Bilan et guides associés
Le meilleur ROI en optimisation CI iOS est souvent de câbler les caches CocoaPods, SPM et DerivedData — avant de changer de puce. Modifiez le workflow aujourd'hui et lancez une PR de test.
Même série :
- CI iOS lent ? Pourquoi xcodebuild traîne sur GitHub Actions — cold/warm, stats P95
- Optimisation runner GitHub Actions macOS : P95 −57 % + playbook CI iOS — P95 −57 %, waterfall Shadow
- Acheter vs louer · 500 builds iOS par mois : faut-il acheter un Mac mini ou louer un Cloud Mac Server ?
- 2026 : pourquoi la CI/CD iOS tourne sur Mac mini M4 · FAQ branchement CI/CD
Des chemins DerivedData et Pods fixes ?
Vuncloud Cloud Mac M4 Pro avec disque données 1 To — pensé pour CI iOS self-hosted : moins de shuffle via actions/cache, warm builds plus stables.