VPSSpark Blog
← Back to Dev Diary

iOS CI Build Time Optimization: GitHub Actions + Xcode 28 to 9 Minutes

Server Notes · 2026.06.06 · ~10 min read

Developer reviewing iOS CI build speed results on MacBook—Xcode build time from 28 to 9 minutes
Twenty-eight minutes per push felt normal—until we split the timeline and found where time actually went.

On a typical GitHub Actions + Xcode iOS project, slow iOS CI build times are often treated as inevitable. They are not. The real issue is that iOS CI slow ≠ Xcode slow — the pipeline is stateless, uncached, and serial by design.

This post walks through a real 8-person iOS team (460K lines of Swift, 28 Pods, 14 SPM packages, 30+ pushes per day) that cut GitHub Actions macOS CI from 28 minutes to 9, with a complete iOS CI optimization playbook you can copy.

28→9
Average build time (min)
68%
Total reduction
74%
Savings from cache + parallel

1. Problem definition: why is iOS CI slow?

Most teams accept 20–30 minute feedback loops as normal. The root cause is not Xcode — it is how the CI pipeline is structured.

1.1 The core issue with GitHub Actions CI

GitHub Actions macOS runners are an ephemeral environment. Every run starts from scratch, which means:

  • ❌ DerivedData is wiped (no incremental build)
  • ❌ CocoaPods runs a full install every time
  • ❌ SPM resolves from scratch every time
  • ❌ Build context cannot be reused

Result: every CI run is a cold build.

1.2 Baseline build breakdown (28 minutes)

We triggered cold runs via workflow_dispatch three times and took the median. Stage-by-stage timing:

StageTimeType
Checkout45sFixed
pod install3m 12sNetwork
SPM resolve1m 44sResolution
xcodebuild build11m 08sCold compile
xcodebuild test6m 22sCPU
xcodebuild archive5m 30sSerial blocker
upload/sign1m 05sFixed
Total29m 46s

Core bottlenecks: three reasons iOS CI is slow

  1. Cold build (39%) — no DerivedData, so 460K lines of Swift recompile every run
  2. Dependency re-resolve (17%) — Pods and SPM re-download and re-parse on every run
  3. Serial pipeline (19%) — test and archive have no dependency yet run one after the other

2. Optimization goals and results

Results at a glance (iOS CI optimization)

StageBuild time
baseline28 min
+ cache20 min
+ parallel12 min
+ Apple Silicon9 min

Where the savings came from

  • Cache: 42% (~8 minutes)
  • Parallelization: 32% (~6 minutes)
  • Apple Silicon: 26% (~3 minutes)

Case study context (E-E-A-T)

ParameterValue
Swift LOC~460K (including tests)
CocoaPods / SPM28 Pods + 14 packages
Baseline runnerGitHub macos-latest (Intel, 4 cores)
Team / cadence8 engineers, 30+ pushes/day
MeasurementTimestamp each step; cold-run median of 3

Single-variable experiments (10 runs per config, median): cache alone −29%, parallelization alone −21%, Apple Silicon alone −18%. Structural changes (cache + parallel) delivered 74% of the gain; hardware raises the ceiling, not the floor. p95 dropped from 33 min to 12 min; DerivedData cache hit rate reached 80%.

3. Optimization #1: CI caching (biggest win)

3.1 Why caching matters most

The heart of iOS CI optimization is making Xcode incremental builds work again in CI. That requires persisting three directories: DerivedData, CocoaPods cache, and Swift Package Manager cache.

3.2 The three cache targets

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

3.3 GitHub Actions configuration

.github/workflows/ios-ci.yml (DerivedData + CocoaPods + SPM cache)
- 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

Hit rate determines whether your DerivedData cache actually pays off. Three common key strategies:

Key strategyHit rateIssue
runner.os only~100%May restore stale Pods; inconsistent artifacts
Lockfile hash (recommended)~80%Hits when deps unchanged; rebuilds only on update
Includes commit SHA~0%Every push misses — effectively no cache

Hash Podfile.lock and Package.resolved as the primary key, and add restore-keys prefix fallbacks so a primary miss still restores the last good cache and triggers incremental compile instead of a full cold build. In CI, always run pod install --no-repo-update to avoid rewriting lockfiles and busting the cache.

See the GitHub Actions cache documentation; for queue and runner bottlenecks that cache alone cannot fix, read why GitHub Actions macOS runners always queue.

3.5 Cache impact

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

Average savings: 8–10 minutes.

4. Optimization #2: CI parallelization (pipeline optimization)

4.1 The problem

Default CI runs build → test → archive serially. But test and archive do not depend on each other — they can run in parallel.

4.2 Parallel job design

.github/workflows/ios-ci.yml (parallel 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 runs only on main; each job restores its own cache. Manage signing with Fastlane Match. For resource isolation at scale, see how 3 Cloud Macs handle 500 iOS CI builds per day.

4.3 Impact

ModeTime
serial20 min
parallel12 min

Saves 5–7 minutes.

5. Optimization #3: Apple Silicon runners

5.1 Why this comes last

Hardware is not the first lever for slow iOS CI. Without caching, switching to Apple Silicon saves only ~18%. Apply structural fixes first, then upgrade hardware to go from 12 min down to 9.

5.2 Measured gains (cache + parallel already enabled)

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

5.3 Takeaway

Additional savings: 3–5 minutes. Apple Silicon helps most during Swift compile and link phases; see Apple's incremental build guide.

5.4 Three macOS runner options compared

After structural optimization, most teams choose among three GitHub Actions self-hosted macOS runner paths:

OptionBuild timeQueueOps overhead
GitHub macos-latest~20 minHigh (10–20 min at peak)None
Self-hosted Mac mini (Intel)~14 minNoneHigh
Cloud Mac (Apple Silicon)~9 minNoneLow

GitHub-hosted runners are free but queue during peaks, and cache storage is capped at 10 GB per repo — large projects evict frequently. Self-hosted eliminates queues but Intel hardware hits a ceiling. For teams pushing 30+ times a day, a dedicated Apple Silicon node usually has the best ROI: fix the pipeline yourself, then evaluate hardware and queue separately.

6. iOS CI optimization decision tree

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

Reproducible runbook (5 steps):

  1. Establish baseline: add echo "::notice::$(date -u +%H:%M:%S)" to each step; cold-run 3 times and take the median
  2. Add caching: DerivedData + Pods + SPM; run 10 times and track hit rate (target > 60%)
  3. Split the workflow: separate test and archive jobs; archive only on main
  4. Evaluate hardware: consider Apple Silicon only if still > 12 min after structural fixes
  5. Monitor p95: target ≤ 1.5× mean; alert when 20% above baseline

7. Common mistakes (iOS CI optimization)

❌ Xcode incremental build in CI works automatically

Wrong. CI wipes DerivedData unless you explicitly cache it.

❌ upgrade Mac solves CI slow

Wrong. Without caching, hardware gains are limited (~18%).

❌ increase -jobs always faster

Exceeding CPU core count slows builds. On M2, -jobs 6–8 is the sweet spot.

❌ cache key should be exact

Commit SHA causes cache misses every push. Use Podfile.lock / Package.resolved hashes instead.

8. FAQ (GitHub Actions iOS CI)

Q1: Why is GitHub Actions iOS CI so slow?

GitHub Actions macOS runners are stateless ephemeral environments — DerivedData, Pods, and SPM caches do not survive between runs. Your Mac keeps warm caches; CI cold-starts every time, even when local Xcode build times are fast.

Q2: What is the most effective iOS CI optimization?

Cache DerivedData + CocoaPods + SPM. That alone accounts for 42% of the savings (~8–10 minutes) and is the top priority for Xcode build time optimization in CI.

Q3: Can Apple Silicon fix slow CI?

Not on its own — it raises the performance ceiling. Without caching, you save ~18%. Combined with cache and parallelization, build time drops from 12 min to 9.

Q4: Which is slower — Pods or SPM?

CocoaPods is usually slower (3m 12s vs 1m 44s) because of network downloads. Cache both; the gains stack.

Q5: What is the best optimization order?

Cache → Parallelization → Apple Silicon. Together they cut 68%; do not skip steps.

9. Conclusion

Slow iOS CI is not a performance problem — it is a pipeline design problem:

The CI pipeline reuses none of its prior work
Optimal path: ① Cache (biggest win) → ② Parallelization (structural fix) → ③ Apple Silicon (performance ceiling).

Once structural optimization is done, teams that need a dedicated Apple Silicon self-hosted runner without hardware ops can explore the Cloud Mac option — zero queue wait, cache not capped at 10 GB per repo.

Limited offer

The last piece of faster iOS CI: Apple Silicon Cloud Mac

Dedicated runners · monthly billing · no hardware ops · minutes to deploy

Back to home
Limited offer See plans now