Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Parallel Xcode Schemes to Cut CI Build Time

Modern Xcode already parallelizes a single scheme. The scheme editor has a Parallelize Build checkbox, and xcodebuild -help on Xcode 16.4 lists -parallelizeTargets ("build independent targets in parallel"), -jobs NUMBER, -parallel-testing-enabled YES|NO and -maximum-parallel-testing-workers NUMBER. Before you split a workspace into multiple schemes and launch several xcodebuild processes on one Mac runner, measure the single-scheme flow with those flags turned up. Cross-scheme fan-out is useful in a narrow set of cases — distinct independent artifacts, destinations that a single xcodebuild can only test sequentially, or product groups that need different signing identities — and it can easily make a small runner slower, not faster.

This page shows when the fan-out helps, when it hurts, a compile-only orchestration script that will not clobber reruns, and a sizing worksheet for picking the right Figment Mac tier.

Start a Figment Mac for parallel Xcode builds →

What the Built-In Parallelism Already Does

Apple's Build targets in parallel help page says enabling Parallelize Build on a scheme makes "the build system choose the best order for building targets, based on the dependencies declared in your project." The caveat on the same page is that enabling it also disables the ordering you configured in Target Dependencies build phases and the scheme's Build action — so your dependency declarations have to be accurate.

On the command line, a single xcodebuild -workspace W.xcworkspace -scheme S build -parallelizeTargets -jobs <N> already keeps independent frameworks compiling in parallel across the cores you give it. For tests, -parallel-testing-enabled YES farms test classes to several XCTest workers inside one invocation. That is the baseline. If the single-scheme baseline is fast enough, you do not need the rest of this article — add the flags and move on.

Apple's command-line guide explains workspace, scheme and destination selection. For parallel testing, inspect xcodebuild -help from the exact Xcode release on your runner. Compare its built-in test scheduling with your proposed fan-out before changing the pipeline.

When Fan-Out Across Schemes Actually Helps

A cross-scheme fan-out is worth the orchestration cost in a short list of situations:

  • The workspace produces more than one independent artifact — for example a main app plus a separately distributed framework, CLI or SDK — that you want to build concurrently rather than in sequential pipeline stages.
  • Your measurements show a gap that the installed Xcode release's built-in multi-destination test scheduling does not solve.
  • Different product groups must use different signing identities, provisioning profiles or configurations, so a single xcodebuild invocation cannot cover them.

Where fan-out hurts: concurrent xcodebuild processes do not share Swift Package resolution, module caches, DerivedData or indexing state. Each extra process re-fetches packages, re-parses modules and competes for memory at link time. On a runner with two shared vCPUs and 8 GiB of RAM, three concurrent xcodebuild processes can be slower than one scheme with -parallelizeTargets -jobs 2, because they compete for CPU time and all hold working data in RAM. Watch peak resident memory — if it approaches total RAM, the host is swapping and wall-clock time goes up, not down.

In other words: the built-in scheduler is cheap, aware of your dependency graph, and already running. Fan-out is an orchestration layer on top of it; treat it as a last resort for cases the built-in scheduler cannot express.

Orchestrating Fan-Out Safely

The script below runs a compile-only fan-out. It takes the workspace as the first argument and each scheme name as its own quoted argument, so scheme names with spaces survive. Nothing in the script rm -rfs a path derived from a scheme name. Each child gets its own index directory under a mktemp run directory, with its own -derivedDataPath and its own -resultBundlePath. There is no default destination and no default scheme. A trap kills still-running children on SIGINT or SIGTERM. Every child's exit status is checked, and the parent exits non-zero if any build failed, so a broken scheme can never be counted as a fast build.

There is no xcresulttool merge call. xcrun xcresulttool merge --help on Xcode 16.4 shows --output-path <output-path> <paths> ... for two or more bundles, but a merged bundle is rarely what CI wants — attach each per-scheme bundle as its own artifact and keep them legible.

#!/usr/bin/env bash
# parallel-build.sh — compile-only fan-out of xcodebuild across shared schemes.
#
# Usage:
#   ./scripts/parallel-build.sh <workspace.xcworkspace> <scheme1> [scheme2 ...]
#
# No default scheme, no default destination. Run once on your real project,
# read the per-scheme logs, then wire into CI.

set -euo pipefail

if [[ $# -lt 2 ]]; then
  echo "Usage: $0 <workspace.xcworkspace> <scheme1> [scheme2 ...]" >&2
  exit 64
fi

WORKSPACE="$1"; shift
if [[ ! -d "$WORKSPACE" ]]; then
  echo "Workspace not found: $WORKSPACE" >&2
  exit 66
fi

RUN_DIR="$(mktemp -d -t parallel-xcodebuild.XXXXXX)"
echo "Run directory: $RUN_DIR"

pids=()
schemes=()

cleanup() {
  # Bash's job table contains this shell's children, not old reused PID values.
  local pid
  for pid in $(jobs -pr); do kill "$pid" 2>/dev/null || true; done
  wait || true
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

i=0
for scheme in "$@"; do
  # Reject empty names, path separators and newlines — defence in depth,
  # since the scheme name is never used to form a path but we still do
  # not want to pass something weird to xcodebuild.
  if [[ -z "$scheme" || "$scheme" == */* || "$scheme" == *$'\n'* ]]; then
    echo "Invalid scheme name: $scheme" >&2
    cleanup
    exit 65
  fi

  idx_dir="$RUN_DIR/$i"
  mkdir -p "$idx_dir"
  log="$idx_dir/build.log"
  derived="$idx_dir/DerivedData"
  result="$idx_dir/result.xcresult"

  echo "Starting [$i] scheme=$scheme"
  xcodebuild build \
    -workspace "$WORKSPACE" \
    -scheme "$scheme" \
    -derivedDataPath "$derived" \
    -resultBundlePath "$result" \
    > "$log" 2>&1 &
  pids+=("$!")
  schemes+=("$scheme")
  i=$((i + 1))
done

failed=0
for i in "${!pids[@]}"; do
  pid="${pids[$i]}"
  scheme="${schemes[$i]}"
  if wait "$pid"; then
    echo "OK   [$i] $scheme"
  else
    rc=$?
    echo "FAIL [$i] $scheme (exit $rc, log: $RUN_DIR/$i/build.log)" >&2
    failed=$((failed + 1))
  fi
done

if (( failed > 0 )); then
  echo "$failed scheme(s) failed; run directory kept at $RUN_DIR" >&2
  exit 1
fi

echo "All schemes built. Run directory: $RUN_DIR"

This is an unexecuted example. Pass your own workspace and the exact scheme names that xcodebuild -list -workspace W.xcworkspace prints — do not let the script invent either. If you want to extend it to xcodebuild test, add a -destination as an explicit argument from CI configuration; do not bake a default destination into the script or share one destination across concurrent children on the same host.

Measuring Before You Pick a Plan

Run three timing passes on the same idle host and only compare runs that exited with status zero:

  1. One scheme with the built-in scheduler: time xcodebuild build -workspace W.xcworkspace -scheme ALL -parallelizeTargets -jobs <cores>
  2. The fan-out script above across the schemes you actually want to split.
  3. The slowest individual scheme on its own — that is the theoretical floor fan-out can reach.

If (2) is not meaningfully better than (1), the dependency graph is the bottleneck and splitting will not help. If (2) is close to (3), you have hit the real limit for that host. If (2) is worse than (1), you are memory- or core-bound and need a bigger runner or fewer concurrent workers.

InputYour value
Single-scheme real with -parallelizeTargets____ min
Fan-out real across N schemes____ min
Slowest single scheme real on its own____ min
Peak resident memory across concurrent processes____ GiB
Available RAM on the runner____ GiB
Cores available to the runner____

If peak resident memory is close to available RAM, drop a worker or move to a larger plan. If (2) is within ten percent of (1), stay on one scheme and invest the engineering time in test parallelization (-parallel-testing-enabled YES) or dependency cleanup instead.

Picking a Figment Mac for This Work

Figment competes directly on Mac hosting. Pick the plan by what your measured workload needs, not by headline price alone. All figures are USD per month, list.

Figment planWhat you getFit for parallel Xcode work
Virtual Intel Mac — $292 vCPU, 8 GiB RAM, 80 GiB disk, browser-console accessA single scheme with built-in parallelism on Intel-compatible Xcode. Two shared vCPUs and 8 GiB are a tight ceiling — not a fit for aggressive multi-process fan-out.
i3 Mac — $49Physical Intel tier, request-basedCollecting requests until supply is confirmed
M-series Mac — from $95Apple silicon entry; exact chip, RAM and disk confirmed in the quoteRequired for ARM-only simulators, visionOS, Xcode 27 and sustained fan-out
Managed Build Mac — $249One M-series Mac with CI setup, backups, monitoring, 30 minutes monthly supportTeams that want the CI pipeline configured for them
Managed Mac Team — $999Two M-series Macs, two CI runners, team setup, backups, monitoring, one hour monthly supportMultiple parallel pipelines across a squad

The $29 virtual Intel Mac is a Linux/KVM-backed Intel macOS environment with no GPU acceleration and no audio. Access is through the browser console; inbound public SSH is not offered on this plan, and outbound CI agents connect from the VM's desktop terminal to your CI service. It is fine for a single-scheme compile with Xcode 26 or earlier on Intel — Apple's Porting your macOS apps to Apple silicon guide states, "If you need to run Xcode on an Intel-based processor, use Xcode 26 or earlier." For Xcode 27, ARM-only simulators, visionOS targets or sustained fan-out across several concurrent xcodebuild processes, request an M-series plan; the quote confirms exact chip, RAM, disk and current availability.

Start a Figment Mac for parallel Xcode builds →


Related guides:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms