Когда iOS CI в GitHub Actions кажется медленным, дело редко в CPU — чаще в отсутствии кэша или неверных ключах, в трёх местах:
- CocoaPods: нет кэша
Pods/→ каждый run повторяетpod install - Swift Package Manager (SPM): граф зависимостей пересобирается;
.buildстирается на каждом job - Xcode DerivedData: cold build → полная компиляция Swift и пересборка модулей
Правильно настроив кэш CocoaPods в GitHub Actions, кэш SPM и кэш DerivedData, warm build часто ускоряется на 30 %–60 % (сравнение Shadow в waterfall-разборе).
1. Почему iOS CI в GitHub Actions тормозит
Первый вопрос в команде: «macos-latest слабый?» «Пора на M4 или мощнее?»
В реальных случаях замедления Xcode CI узкое место редко — CPU. Это три скрытые затраты:
- Повторное разрешение зависимостей (CocoaPods / SPM)
- Cold start DerivedData (полная перекомпиляция)
- Промахи actions/cache — каждый run как первый build
GitHub Actions усиливает эффект: runner'ы эфемерные (чистый диск на job), кэш сам не «прилипает», multi-branch / multi-job портят ключи. Если ищете почему один и тот же коммит то быстро, то медленно собирается, сначала разделите cold и warm — затем настройте кэш по этой заметке.
2. Куда реально уходит время iOS CI
2.1 CocoaPods (кэш Pods)
Типичная cold-стоимость: pod install около 3–8 минут.
Причина: каждый CI-run снова запускает pod install — resolve, download, integrate с нуля.
Исправление: кэшировать Pods/ через actions/cache; key = хэш Podfile.lock + github.ref + arm64. В CI — pod install --deployment.
2.2 Swift Package Manager (SPM)
Типичная стоимость: разрешение графа пакетов 1–5 минут.
Причина: переразрешение каждый run; .build удалён; Package.resolved не в cache key.
Исправление: кэшировать ~/Library/Caches/org.swift.swiftpm и .build; key включает хэш Package.resolved (см. §3.2).
2.3 Xcode DerivedData
Типичная cold-добавка: полная или почти полная компиляция 5–15 минут.
Причина: DerivedData не переиспользуется → полный Swift compile, повторная обработка assets, rebuild модулей.
Исправление: xcodebuild -derivedDataPath DerivedData и кэшировать этот путь (см. §3.3).
3. Рабочая настройка кэша iOS CI в GitHub Actions
Для runs-on: macos-latest. Все три блока в один job, до pod install / xcodebuild (actions/cache@v4 сохраняет автоматически).
3.1 Кэш CocoaPods (обязательно)
- 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 Кэш SPM (обязательно)
- 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 Кэш DerivedData (высокий 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 ...
Перед сравнением времени шагов дождитесь Cache hit occurred в логах. При miss сначала исправьте key и path — не спешите винить версию Xcode.
4. Проектирование cache key (не пропускайте)
Многие усилия по оптимизации iOS CI ломаются на ключах, а не на runner'ах.
| ✔ Обязательно | ❌ Типичные ошибки |
|---|---|
Архитектура (arm64) |
Нет split arm64/x86 — сменили runner, всё miss |
Ветка (github.ref) |
Только lockfile — DerivedData смешивается между ветками |
| Хэш lockfile | restore-keys слишком широкие (напр. только dd-) → ложный hit |
При смене lockfile префикс вроде dd-arm64-${{ github.ref }}- может частично переиспользовать кэш. Несколько scheme? Добавьте ${{ matrix.scheme }} в key. Параллельные jobs на одном runner не должны делить один root DerivedData — изолируйте подкаталогами ${{ github.run_id }}.
5. Self-hosted / Cloud Mac (продвинутый уровень)
На Mac mini или Cloud Mac как self-hosted runner вы не ограничены actions/cache — диск сохраняется, warm build стабильнее:
- Фиксированный путь DerivedData
- Фиксированный каталог Pods
- Warm-кэш на диске (экономит 30–90 с upload/download на job)
export DERIVED_DATA=/Volumes/Data/DerivedData/App-${{ github.run_id }}
export PODS_ROOT=/Volumes/Data/Pods/${{ github.ref_name }}
Полный пример job и диск данных 1 ТБ: workflow ниже и FAQ подключения 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 (частая ошибка)
Команды часто списывают медленный iOS CI в GitHub Actions на runner или версию Xcode. Чаще:
| Что видите | Более вероятная причина |
|---|---|
| Build внезапно медленнее | Cache miss |
| P95 «плавает» | Cold build'ы в warm-статистике |
| Случайные замедления | Конкуренция DerivedData между параллельными jobs |
| Зависимости с нуля | Неверный cache key |
Сочетайте с гайдом по дисперсии сборки: cold/warm → настройка кэша → затем железо (2026: почему iOS CI/CD работает на Mac mini M4).
7. Измеренные выигрыши
Только кэш, типичная экономия:
| Слой | Доп. время при cold miss | После настройки кэша |
|---|---|---|
| CocoaPods | 3–8 мин | warm часто <30 с–3 мин |
| SPM | 1–5 мин | resolve заметно короче при hit |
| DerivedData | 5–15 мин | warm в основном инкрементальная компиляция |
В нашем 14-дневном Shadow-run только оптимизация кэша сократила warm wall clock примерно на −1:40. Убрали очередь и self-hosted с фиксированным диском — warm P95 14:12 → 6:05 (−57 %), см. обзор оптимизации.
8. FAQ
Почему iOS CI в GitHub Actions такой медленный?
Обычно не CPU — CocoaPods / SPM / DerivedData без кэша или с неверными ключами. Эфемерные runner'ы усиливают каждый miss.
Как кэшировать CocoaPods в GitHub Actions?
Кэшировать Pods/; key: pods-arm64-${{ github.ref }}-${{ hashFiles('**/Podfile.lock') }}. См. §3.1.
Как кэшировать SPM в iOS CI?
Кэшировать глобальный каталог swiftpm и .build; привязать key к Package.resolved. См. §3.2.
Какой путь использовать для кэша DerivedData?
path в actions/cache должен совпадать с xcodebuild -derivedDataPath; системный default не обязателен.
Кэш слишком большой?
GitHub ограничивает запись примерно 10 ГБ. Разбейте DerivedData keys по scheme или self-hosted с фиксированным диском без upload.
macos-latest vs self-hosted?
Hosted: только actions/cache, может очищаться между jobs. Self-hosted: фиксированный /Volumes/Data, стабильнее warm build. Сравнение в обзоре оптимизации.
9. Итог и связанные гайды
Максимальный ROI в оптимизации iOS CI часто даёт настройка кэшей CocoaPods, SPM и DerivedData — до смены чипа. Меняйте workflow сегодня и гоняйте тестовый PR.
Та же серия:
- iOS CI тормозит? Почему xcodebuild на GitHub Actions «плавает» — cold/warm, статистика P95
- Оптимизация runner GitHub Actions macOS: P95 −57 % + playbook iOS CI — P95 −57 %, Shadow waterfall
- Купить vs арендовать · 500 iOS-билдов в месяц — купить Mac mini или арендовать Mac Cloud Server?
- 2026: почему iOS CI/CD работает на Mac mini M4 · FAQ подключения CI/CD
Нужны фиксированные пути DerivedData и Pods?
Vuncloud Cloud Mac M4 Pro с диском данных 1 ТБ — для self-hosted iOS CI: меньше перекладывания через actions/cache, стабильнее warm build.