VPSSpark Блог
← К дневнику разработки

Оптимизация iOS CI: GitHub Actions + Xcode с 28 до 9 минут

Заметки с сервера · 2026.06.06 · ~10 мин

Разработчик смотрит результаты ускорения iOS CI на MacBook
28 минут на push казались нормой—пока не разложили timeline по шагам.

В типичном iOS-проекте на GitHub Actions и Xcode долгая сборка iOS CI часто считается нормой. На деле iOS CI slow ≠ Xcode slow — корень в stateless pipeline без кэша и последовательной архитектуре.

Статья основана на реальном кейсе команды из 8 iOS-разработчиков (460 000 строк Swift, 28 Pods, 14 SPM-пакетов, 30+ push в день): GitHub Actions macOS CI сократили с 28 до 9 минут — с полным планом iOS CI optimization.

28→9
Среднее время сборки (мин)
68%
Общее сокращение
74%
за счёт кэша и параллелизации

1. Постановка задачи: почему iOS CI тормозит?

Многие команды мирятся с 20–30 минутами ожидания. Узкое место — не Xcode, а структура CI pipeline.

1.1 Суть проблемы GitHub Actions CI

macOS runner в GitHub Actions — это эфемерная среда (ephemeral environment). Каждый запуск означает:

  • ❌ Потеря DerivedData — нет инкрементальной сборки
  • ❌ CocoaPods переустанавливается каждый раз
  • ❌ SPM resolve заново на каждом запуске
  • ❌ Build context не переиспользуется

Итог: каждый запуск — cold build.

1.2 Разбивка baseline (28 минут)

Холодный запуск через workflow_dispatch, 3 прогона, медиана по фазам:

ФазаВремяТип
Checkout45sфикс.
pod install3m 12sсеть
SPM resolve1m 44sresolve
xcodebuild build11m 08scold compile
xcodebuild test6m 22sCPU
xcodebuild archive5m 30sпоследовательная блокировка
upload/sign1m 05sфикс.
Итого29m 46s

Ключевые узкие места: три причины медленной iOS CI

  1. Cold Build (39 %) — без DerivedData каждый раз компилируются все 460 000 строк Swift
  2. Dependency Re-resolve (17 %) — Pods и SPM заново скачиваются и резолвятся
  3. Serial Pipeline (19 %) — test и archive идут подряд, хотя зависимости между ними нет

2. Цель и результат

Результаты оптимизации (обзор 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~460 000 строк (с тестами)
CocoaPods / SPM28 Pods + 14 packages
Baseline runnerGitHub macos-latest (Intel, 4 ядра)
Команда / частота8 человек, 30+ push/день
Методикатаймстамп на step, 3 холодных прогона, медиана

Однофакторные тесты (10 прогонов на конфиг, медиана): только кэш −29 %, только параллелизация −21 %, только Apple Silicon −18 %. Структурная оптимизация (кэш + параллель) даёт 74 % — железо задаёт потолок, но не является первым рычагом. p95 снизился с 33 до 12 мин, hit rate DerivedData — 80 %.

3. Рычаг 1: кэш CI (максимальный эффект)

3.1 Почему кэш критичен

Суть iOS CI optimization: вернуть инкрементальную сборку Xcode в CI. Нужно персистить DerivedData, кэш CocoaPods и кэш Swift Package Manager.

3.2 Три каталога для кэширования

  • 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 Дизайн cache key

Hit rate определяет, работает ли DerivedData cache по-настоящему. Три типичные стратегии:

Стратегия keyHit rateПроблема
только runner.os~100 %устаревшие Pods, несогласованные артефакты
хэш lock-файлов (рекомендуется)~80 %hit при неизменных зависимостях, rebuild только при обновлении
commit SHA в key~0 %miss на каждом push — кэш бесполезен

Рекомендация: хэш Podfile.lock / Package.resolved как primary key плюс restore-keys для prefix match. При miss primary key остаётся предыдущий валидный кэш — инкрементальная сборка вместо полного cold build. В CI обязательно pod install --no-repo-update, чтобы не инвалидировать кэш перезаписью lock-файлов.

Подробнее — документация GitHub Actions cache; сравнение удалённого и локального кэша — DerivedData / Pods / sccache.

3.5 Эффект кэширования

Шагдопосле
pod install3m 12s50s
SPM resolve1m 44s15s
build11m3–4m

В среднем экономия 8–10 минут.

4. Рычаг 2: параллелизация CI (pipeline optimization)

4.1 Проблема

Стандартный CI: build → test → archive (последовательно). При этом test и archive не зависят друг от друга — их можно запускать параллельно.

4.2 Параллельные jobs

.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. Изоляция ресурсов: 3 Cloud Mac на 500 iOS CI сборок в день.

4.3 Эффект

РежимВремя
serial20 min
parallel12 min

Экономия 5–7 минут.

5. Рычаг 3: Apple Silicon runner

5.1 Почему железо — последний шаг

Железо — не первый ответ на медленную iOS CI. Без кэша 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 по инкрементальным сборкам.

5.4 Сравнение трёх вариантов macOS runner

После структурной оптимизации команды выбирают между тремя подходами GitHub Actions self-hosted runner macOS:

ВариантВремя сборкиОчередьЗатраты на поддержку
GitHub macos-latest~20 minвысокая (пики: 10–20 мин ожидания)нулевые
Свой Mac mini (Intel)~14 minнетвысокие
Cloud Mac (Apple Silicon)~9 minнетнизкие

Hosted runner GitHub бесплатен, но очередь macOS runner становится узким местом; кэш ограничен 10 ГБ на repo — крупные проекты часто теряют кэш. Self-hosted убирает очередь, но Intel упирается в потолок. При 30+ push/день выделенный Apple Silicon узел обычно даёт лучший ROI: сначала оптимизируйте структуру, железо и очередь оценивайте отдельно.

6. Дерево решений 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. Замерить baseline: echo "::notice::$(date -u +%H:%M:%S)" на каждый step, 3 холодных прогона, медиана
  2. Включить кэш: DerivedData + Pods + SPM, 10 прогонов, цель hit rate > 60 %
  3. Разделить workflow: test / archive — отдельные jobs, archive только на main
  4. Оценить железо: Apple Silicon runner — если после структуры всё ещё > 12 min
  5. Мониторить p95: цель ≤ 1,5× среднего, алерт при +20 % к baseline

7. Типичные ошибки (iOS CI Optimization Mistakes)

❌ Xcode incremental build in CI works automatically

Неверно. Без явного кэша DerivedData инкрементальной сборки в CI не будет.

❌ upgrade Mac solves CI slow

Неверно. Без кэша выигрыш ~18 %.

❌ increase -jobs always faster

Выше числа ядер CPU — медленнее. На M2 рекомендуем -jobs 6~8.

❌ cache key should be exact

Commit SHA даёт cache miss. Используйте хэш Podfile.lock / Package.resolved.

8. FAQ (частые вопросы по GitHub Actions iOS CI)

В1: Почему GitHub Actions iOS CI такой медленный?

macOS runner — stateless ephemeral environment: DerivedData, Pods и SPM-кэш теряются после каждого запуска. Локально Xcode собирает быстро с тёплым кэшем; в CI каждый раз старт с нуля.

В2: Что эффективнее всего для iOS CI optimization?

Кэш DerivedData + CocoaPods + SPM — 42 % выигрыша, 8–10 минут в среднем. Это первый приоритет для Xcode build time optimization в CI.

В3: Apple Silicon решит проблему сам по себе?

Нет — он задаёт только потолок производительности. Без кэша ~18 %; с кэшем и параллелизацией — с 12 до 9 минут.

В4: Pods или SPM — что медленнее?

Обычно CocoaPods (3m 12s vs. 1m 44s) из-за сетевых загрузок. Кэшировать нужно оба — эффекты складываются.

В5: Оптимальный порядок?

Cache → Parallelization → Apple Silicon. Вместе — 68 % сокращения; порядок не пропускать.

9. Заключение

Медленная iOS CI — не чисто performance-проблема, а:

Pipeline не переиспользует результаты предыдущих вычислений
Оптимальный путь: ① Cache (максимальный эффект) → ② Parallelization (структура) → ③ Apple Silicon (потолок).

После структурной оптимизации: если нужен выделенный Apple Silicon self-hosted runner без ops по железу — см. решение Cloud Mac (нулевая очередь, кэш без лимита 10 ГБ/repo).

Акция

Последний шаг к быстрой iOS CI: Cloud Mac на Apple Silicon

Выделенные runner · подписка · без обслуживания железа

На главную
Акция Смотреть тарифы