From axiom
Optimizes Xcode build performance for Swift/iOS/macOS apps: measure baselines, analyze Build Timelines, fix bottlenecks, enable compilation caching.
npx claudepluginhub charleswiltgen/axiom --plugin axiomThis skill uses the workspace's default tool permissions.
Systematic Xcode build performance analysis and optimization. **Core principle**: Measure before optimizing, then optimize the critical path first.
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
Systematic Xcode build performance analysis and optimization. Core principle: Measure before optimizing, then optimize the critical path first.
For automated scanning and quick wins:
/axiom:optimize-build
The build-optimizer agent scans for common issues and provides immediate fixes. Use this skill for deep analysis.
Why: You can't improve what you don't measure. Baseline prevents placebo optimizations.
# Clean build (eliminates all caching)
xcodebuild clean build -scheme YourScheme
# Measure time
time xcodebuild build -scheme YourScheme
# Or use Xcode UI
Product → Perform Action → Build with Timing Summary
Record:
Example baseline:
Clean build: 247 seconds
Incremental (1 file change): 12 seconds
Longest phase: Compile Swift sources (189s)
Access:
What to look for:
The critical path is the shortest possible build time with unlimited CPU cores. It's defined by the longest chain of dependent tasks.
┌─────────────────────────────────────────┐
│ Critical Path: A → B → C → D (120s) │
│ │
│ Task A: 30s ─────────┐ │
│ Task B: 40s ├─→ D: 20s │
│ Task C: 30s ─────────┘ │
│ │
│ Even with 100 CPUs, build takes 120s │
└─────────────────────────────────────────┘
Goal: Shorten the critical path by breaking dependencies.
Empty vertical space: Tasks waiting for inputs
Timeline:
████████░░░░░░░░████████ ← Bad: idle cores waiting
████████████████████████ ← Good: continuous work
Long horizontal bars: Slow individual tasks
Task A: ████████████████████ (45 seconds) ← Investigate
Task B: ███ (3 seconds) ← Fine
Serial target builds: Targets waiting unnecessarily
Framework: ████████░░░░░░░░░░ ← Waiting
App: ░░░░░░░░░░████████ ← Delayed
Better (parallel):
Framework: ████████
App: ░░░░████████████
Is compilation the slowest phase? ├─ YES → Check type checking performance (Step 4) └─ NO → Is linking slow? ├─ YES → Check link dependencies (Step 5) └─ NO → Are scripts slow? ├─ YES → Optimize build phase scripts (Step 6) └─ NO → Check parallelization (Step 7)
Symptom: "Compile Swift sources" takes >50% of build time.
Diagnosis:
Enable compiler warnings to find slow functions:
// Add to Debug build settings → Other Swift Flags
-warn-long-function-bodies 100
-warn-long-expression-type-checking 100
Build → Xcode shows warnings:
MyView.swift:42: Function body took 247ms to type-check (limit: 100ms)
LoginViewModel.swift:18: Expression took 156ms to type-check (limit: 100ms)
Fix slow type checking:
// ❌ SLOW - Complex type inference (247ms)
func calculateTotal(items: [Item]) -> Double {
return items
.filter { $0.isActive }
.map { $0.price * $0.quantity }
.reduce(0, +)
}
// ✅ FAST - Explicit types (12ms)
func calculateTotal(items: [Item]) -> Double {
let activeItems: [Item] = items.filter { $0.isActive }
let prices: [Double] = activeItems.map { $0.price * $0.quantity }
let total: Double = prices.reduce(0, +)
return total
}
Common slow patterns:
Expected impact: 10-30% faster compilation for affected files.
Symptom: Build Timeline shows long script phases in Debug builds.
Common culprits:
Fix: Make scripts conditional
# ❌ BAD - Runs in ALL configurations (adds 6+ seconds to debug builds)
#!/bin/bash
firebase crashlytics upload-symbols
# ✅ GOOD - Skip in Debug
#!/bin/bash
if [ "${CONFIGURATION}" = "Release" ]; then
firebase crashlytics upload-symbols
fi
# Example savings: 6.3 seconds per incremental debug build
Script Phase Sandboxing (Xcode 14+)
Enable to prevent data races and improve parallelization:
Build Settings → User Script Sandboxing → YES
Why: Forces you to declare inputs/outputs explicitly, enabling parallel execution.
# Script phase with proper inputs/outputs
Input Files:
$(SRCROOT)/input.txt
$(DERIVED_FILE_DIR)/checksum.txt
Output Files:
$(DERIVED_FILE_DIR)/output.html
# Now Xcode knows dependencies and can parallelize safely
Parallel Script Execution:
Build Settings → FUSE_BUILD_SCRIPT_PHASES → YES
⚠️ WARNING: Only enable if ALL scripts have correct inputs/outputs declared. Otherwise you'll get data races.
Expected impact: 5-10 seconds saved per incremental debug build.
Symptom: Incremental builds recompile entire modules.
Check current settings:
# In project.pbxproj
grep "SWIFT_COMPILATION_MODE" project.pbxproj
Optimal configuration:
| Configuration | Setting | Why |
|---|---|---|
| Debug | singlefile (Incremental) | Only recompiles changed files |
| Release | wholemodule | Maximum optimization |
// ❌ BAD - Whole module in Debug
SWIFT_COMPILATION_MODE = wholemodule; // ALL configs
// ✅ GOOD - Incremental for Debug
Debug: SWIFT_COMPILATION_MODE = singlefile;
Release: SWIFT_COMPILATION_MODE = wholemodule;
How to fix:
Expected impact: 40-60% faster incremental debug builds.
Symptom: Debug builds compile for multiple architectures (x86_64 + arm64).
Check:
grep "ONLY_ACTIVE_ARCH" project.pbxproj
Fix:
| Configuration | Setting | Why |
|---|---|---|
| Debug | YES | Only build for current device (arm64 OR x86_64) |
| Release | NO | Build universal binary |
How to fix:
Expected impact: 40-50% faster debug builds (half the architectures).
Symptom: Debug builds generating dSYMs unnecessarily.
Optimal configuration:
| Configuration | Setting | Why |
|---|---|---|
| Debug | dwarf | Embedded debug info, faster |
| Release | dwarf-with-dsym | Separate dSYM for crash reporting |
# Check current
grep "DEBUG_INFORMATION_FORMAT" project.pbxproj
How to fix:
Expected impact: 3-5 seconds saved per debug build.
Symptom: Build Timeline shows targets building sequentially when they could be parallel.
Check scheme configuration:
Dependency graph example:
App ──┬──→ Framework A
└──→ Framework B
Framework A ──→ Utilities
Framework B ──→ Utilities
Timeline (bad - serial):
Utilities: ████████░░░░░░░░░░░░░░
Framework A: ░░░░░░░░████████░░░░░░
Framework B: ░░░░░░░░░░░░░░░░████████
App: ░░░░░░░░░░░░░░░░░░░░░░████
Timeline (good - parallel):
Utilities: ████████
Framework A: ░░░░░░░░████████
Framework B: ░░░░░░░░████████
App: ░░░░░░░░░░░░░░░░████
Expected impact: Proportional to number of independent targets (e.g., 2 parallel targets = ~2x faster).
What it is: Swift modules are produced separately from compilation, unblocking downstream targets faster.
Before (Xcode 13):
Framework: Compile ████████████ → Emit Module █
App: ░░░░░░░░░░░░░░░░░░░░░░░░░█████████
↑
Waiting for Framework compilation to finish
After (Xcode 14+):
Framework: Compile ████████████
Emit Module ███
App: ░░░░░░███████████
↑
Starts as soon as module emitted
Automatic: No configuration needed, works in Xcode 14+ with Swift 5.7+.
Expected impact: Reduces idle time in multi-target builds by 20-40%.
What it is: Linking can start before all compilation finishes if the module is ready.
Impact: Further reduces critical path in dependency chains.
Automatic: Works in Xcode 14+ automatically.
What it is: Xcode 26 introduces compilation caching that reuses previously compiled artifacts across clean builds.
Build Settings:
Build Settings → COMPILATION_CACHE_ENABLE_CACHING → YES
How it works:
xcodebuild clean, cached artifacts can be reusedWhen to enable:
Verification:
# Build with caching enabled
xcodebuild build -scheme YourScheme \
COMPILATION_CACHE_ENABLE_CACHING=YES
# Check build log for cache information
Current limitations (Xcode 26):
Expected impact: 20-40% faster clean builds after initial cache population (up to 70%+ for favorable projects).
What it is: Xcode splits module compilation into explicit build tasks instead of implicit on-demand compilation. Enabled by default for Swift in Xcode 26.
The Problem with Implicit Modules (Pre-Xcode 16):
When a compiler encounters an import, it builds the module on-demand:
Compile A.swift ─── needs UIKit ───→ (builds UIKit.pcm) ───→ continues
Compile B.swift ─── needs UIKit ───→ (waits for A to finish) ───→ uses cached
Compile C.swift ─── needs UIKit ───→ (waits) ───→ uses cached
Problems:
Explicitly Built Modules Solution:
Xcode now separates compilation into three phases:
Phase 1: SCAN Phase 2: BUILD MODULES Phase 3: COMPILE
┌──────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ Scan A.swift │ │ Build UIKit.pcm │ │ Compile A.swift │
│ Scan B.swift │ → │ Build Foundation.pcm │ → │ Compile B.swift │
│ Scan C.swift │ │ Build SwiftUI.pcm │ │ Compile C.swift │
└──────────────────┘ └──────────────────────┘ └──────────────────┘
(fast) (parallel) (parallel)
Benefits:
Enable/Disable (if needed):
Build Settings → Explicitly Built Modules → YES (default in Xcode 26 for Swift)
Module Variants (WWDC 2024-10171)
The same module may be built multiple times with different settings:
Build Log:
Compile Clang module 'UIKit' (hash: abc123) ← Variant 1
Compile Clang module 'UIKit' (hash: def456) ← Variant 2
Compile Swift module 'UIKit' (hash: ghi789) ← Variant 3
Common causes of variants:
Diagnose variants:
Product → Perform Action → Build with Timing SummaryReduce variants (unify settings at project/workspace level):
# Check for macro differences
grep "GCC_PREPROCESSOR_DEFINITIONS" project.pbxproj
# Move target-specific macros to project level where possible
Project → Build Settings → Preprocessor Macros → [unify here]
Example (from WWDC 2024-10171):
Before: 4 UIKit variants (2 Swift × 2 Clang)
After: 2 UIKit variants (unified settings)
Impact: Fewer module builds = faster incremental builds
Expected impact: 10-30% faster builds by reducing duplicate module compilation.
Note: Swift Build (Xcode 26+): Xcode now uses Swift Build, Apple's open-source build engine. This provides more predictable builds, better SPM integration, and cross-platform support (Linux, Windows, Android). No configuration needed.
Required steps:
Baseline (before changes):
xcodebuild clean build -scheme YourScheme 2>&1 | tee baseline.log
Apply ONE optimization at a time
Measure improvement:
xcodebuild clean build -scheme YourScheme 2>&1 | tee optimized.log
Compare:
# Extract build time from logs
grep "Build succeeded" baseline.log
grep "Build succeeded" optimized.log
Example:
Baseline: Build succeeded (247.3 seconds)
Optimized: Build succeeded (156.8 seconds)
Improvement: 90.5 seconds (36.6% faster)
Before optimization:
After optimization:
Critical path: Should be visibly shorter.
Baseline:
Optimizations applied:
Result:
Baseline:
Optimizations applied:
Result:
Mistake: "I think this will help" → make change → no measurement.
Why bad: Placebo improvements, wasted time, actual regressions unnoticed.
Fix: Always measure before → change one thing → measure after.
Mistake: Set Release to incremental compilation for "faster builds".
Why bad: Release builds should optimize for runtime performance, not build speed. You ship Release builds to users.
Fix: Only optimize Debug builds for speed. Keep Release optimized for runtime.
Mistake: Remove legitimate dependencies to "make builds parallel".
Why bad: Build errors, undefined behavior, race conditions.
Fix: Only parallelize truly independent targets. Use Build Timeline to identify safe opportunities.
Mistake: Enable parallel scripts but don't declare inputs/outputs.
Why bad: Data races, non-deterministic build failures, incorrect builds.
Fix: First enable ENABLE_USER_SCRIPT_SANDBOXING = YES, fix all errors, THEN enable FUSE_BUILD_SCRIPT_PHASES.
Check:
xcodebuild clean)Check:
Check:
-warn-long-function-bodies 100 (with hyphen)# Find slowest files to compile
xcodebuild -workspace YourApp.xcworkspace \
-scheme YourScheme \
clean build \
OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" 2>&1 | \
grep ".[0-9]ms" | \
sort -nr | \
head -20
Output:
247.3ms MyViewModel.swift:42:1 func calculateTotal
156.8ms LoginView.swift:18:3 var body
89.2ms NetworkManager.swift:67:1 func handleResponse
...
Action: Add explicit types to slowest functions.
# From build log
Build target 'MyApp' (project 'MyApp')
Compile Swift source files (128.4 seconds)
Link MyApp (12.3 seconds)
Run custom shell script (6.7 seconds)
Action: Optimize the longest phase first.
Before considering your build optimized:
Measurement
Compilation Settings
Parallelization
Xcode 26+ (if applicable)
COMPILATION_CACHE_ENABLE_CACHING)WWDC: 2018-408, 2022-110364, 2024-10171, 2025-247
Docs: /xcode/improving-the-speed-of-incremental-builds, /xcode/building-your-project-with-explicit-module-dependencies
Tools: Xcode Build Timeline (Xcode 14+), Build with Timing Summary (Product → Perform Action), Modules Report (Xcode 16+), Instruments Time Profiler
Remember: Build performance optimization is about systematic measurement and targeted improvements. Optimize the critical path first, measure everything, and verify improvements in the Build Timeline.