Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Profile and Reduce Xcode CI Build Times Guide

Measure before you change a build setting. Teams that tune Xcode build times without profiling usually optimize files that account for a tiny fraction of the total while the real culprit sits untouched. This guide instruments an xcodebuild run, extracts the slowest Swift compile units, and applies targeted fixes — with a short shell script that turns raw log output into a sortable top-10 table.

profile on a Figment M-series Mac (requests from $95/month; exact chip, RAM and disk are confirmed in the quote). A $29 virtual Intel macOS VM (2 vCPU, 8 GiB RAM, 80 GiB) is a legitimate target for lightweight Intel toolchains and for running Xcode 26 or earlier on Intel, but Xcode 27, Apple-silicon-only simulators and parallel Swift compilation at scale belong on an M-series host. The flags and parser below are documented against Apple's guidance in Improving the speed of incremental builds.

Enable Build Timing in xcodebuild

Two layers of timing data combine well. Use both on a full clean build; incremental builds skip unchanged files and distort per-file numbers.

Layer 1 — the build phase summary:

xcodebuild build \
  -scheme "$SCHEME" \
  -destination "platform=iOS Simulator,name=iPhone 16" \
  -showBuildTimingSummary \
  2>&1 | tee build-timing.log

Capture with tee so you can parse it offline. The summary prints at the end and groups wall-clock seconds by phase: CompileSwiftSources, CompileC, PhaseScriptExecution, CodeSign, Ld. That tells you which phase is the bottleneck before you drill into files.

Layer 2 — Swift frontend per-file timers, surfaced through OTHER_SWIFT_FLAGS:

xcodebuild build \
  -scheme "$SCHEME" \
  -destination "platform=iOS Simulator,name=iPhone 16" \
  -showBuildTimingSummary \
  OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies -Xfrontend -debug-time-compilation" \
  2>&1 | tee build-timing-detailed.log
FlagWhat it measures
-Xfrontend -debug-time-function-bodiesType-check time per function body
-Xfrontend -debug-time-compilationFrontend phase time per source file
-Xfrontend -warn-long-function-bodies=100Warning when a function body takes over 100 ms to type-check

Replace "$SCHEME" with the scheme name your project actually defines (from xcodebuild -list). Do not hard-code a scheme you have not verified.

Read the Timing Summary

A typical -showBuildTimingSummary section looks like:

Build Timing Summary
CompileSwiftSources   (2 tasks)      47.3 seconds
CompileC              (12 tasks)      3.1 seconds
PhaseScriptExecution  (3 tasks)       8.4 seconds
CodeSign              (1 task)        0.9 seconds
Ld                    (2 tasks)       1.2 seconds

Here Swift compilation is roughly 78% of total build time (47.3 of 60.9 s), which is where to focus. If PhaseScriptExecution is a large fraction, audit your run script build phases; scripts without declared input and output file lists invalidate incremental state and rerun every build.

parse-build-times.sh

The script below reads a log captured with tee, pulls CompileSwift lines that carry a file path and duration, sorts descending, and prints the top N. It fails fast and does not count failed builds as timings.

#!/usr/bin/env bash
# ci/parse-build-times.sh
# Usage: bash ci/parse-build-times.sh build-timing-detailed.log [N]
set -euo pipefail

LOG_FILE="${1:?Usage: $0 <xcodebuild-log-file> [top-N]}"
TOP_N="${2:-10}"

if [[ ! -f "${LOG_FILE}" ]]; then
  echo "ERROR: log file not found: ${LOG_FILE}" >&2
  exit 1
fi

# Confirm the captured build succeeded. A failed build has misleading timings.
if grep -qE "^\*\* BUILD FAILED \*\*|xcodebuild: error:" "${LOG_FILE}"; then
  echo "ERROR: ${LOG_FILE} contains a failed build; refusing to report timings." >&2
  exit 2
fi

echo "==> Top ${TOP_N} slowest CompileSwift files in ${LOG_FILE}"
awk '
  /^CompileSwift / {
    for (i = 1; i <= NF; i++) if ($i ~ /\.swift$/) { path = $i }
    match($0, /\(([0-9]+\.[0-9]+) seconds\)/, m)
    if (path != "" && m[1] != "") printf "%s\t%s\n", m[1], path
    path = ""
  }
' "${LOG_FILE}" \
  | sort -rn \
  | head -n "${TOP_N}" \
  | awk 'BEGIN{printf "%-10s %s\n","SECONDS","FILE"} {printf "%-10s %s\n",$1,$2}'

Run it on demand, not on every CI push — the diagnostic flags themselves add measurable overhead.

bash ci/parse-build-times.sh build-timing-detailed.log 10

Common Causes and Targeted Fixes

Once the top-10 list is in hand, look for these patterns:

Complex type inference. A long fluent chain like items.filter { ... }.map { ... }.reduce(0) { ... } forces the solver to infer every intermediate type. Split it:

let active: [Item] = items.filter { $0.isActive }
let values: [Int]  = active.map { $0.value }
let total: Int     = values.reduce(0, +)

Large files with heavy generics. Files over roughly 1,000 lines with nested generics or protocol extensions with associated types are reliable hot spots. Split them into smaller translation units.

Unconditional run script phases. A phase that runs pod install or SwiftLint on every build without declared input and output file lists invalidates the build graph. Declare the files so Xcode can skip unchanged runs.

Whole Module Optimization in Debug. SWIFT_COMPILATION_MODE = wholemodule is a Release setting; it maximises optimisation at the cost of single-threaded compilation. Confirm Debug is incremental in your .xcconfig or build settings pane.

FixEffortWhere it helps
Explicit types on hot filesLowFiles in the top-10 with long solver time
Split files > 800 lines with heavy genericsMediumLargest files in top-10
Input/output file lists on run scriptsLowBuilds with 3+ run script phases
Debug SWIFT_COMPILATION_MODE=incrementalVery lowProjects that inherited wholemodule into Debug
Clang module cachingMediumMixed Swift/Obj-C projects

Use parse-build-times.sh before and after each change on the same host and scheme to measure the actual delta. Numbers reported by other teams on other hardware are not your baseline.

Pick the Right Host for Profiling

Build-time results are only comparable on identical hardware with the same caches warm. A hosted Mac that keeps DerivedData, the SwiftPM .build directory and the signing keychain on one disk between runs makes trend tracking meaningful. Caches to preserve between runs:

  • ~/Library/Developer/Xcode/DerivedData keyed by scheme and git ref.
  • The package .build directory for SwiftPM dependency artifacts.
  • Signing keychains and provisioning profiles, kept per-job and restored from your CI secret manager (do not dump the environment to a file; transfer secret names and restore values).

Avoid xcodebuild clean except when explicitly testing a cold build.

A Worked Cost Worksheet for Profiling Work

Treat profiling as a short, bounded project and price it against a hosted Mac term. The arithmetic below uses verified Figment entry prices and is independent of any benchmark claim.

  • Two-week profiling spike on a $29 virtual Intel macOS instance for Intel-compatible targets (Xcode 26 or earlier): budget $29 for the monthly subscription. Using it for two weeks does not turn the monthly plan into a daily or prorated offer.
  • Three-month profiling and migration on an M-series host at the $95/month entry price: $95 × 3 = $285. The exact chip, RAM and disk at that entry are confirmed in the quote; do not assume parity with a specific competitor SKU.
  • Twelve-month hosted CI on the Managed Build Mac at $249/month: $249 × 12 = $2,988. Includes one M-series Mac, CI setup, backups, monitoring and 30 minutes of monthly support.
  • If the plan is a two-Mac team runner for twelve months on Managed Mac Team at $999/month: $999 × 12 = $11,988. Includes two M-series Macs, two CI runners, team setup, backups, monitoring and one hour of monthly support.

Match the plan to the specific CI work. A profiling spike that produces the top-10 list and two fixes rarely needs the team plan; a permanent shared runner usually does.

See Mac hosting plans at /mac-hosting or start a configuration at /mac-hosting/start.

Related guides:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms