One day while hanging out with my friends, we started guessing:
“How much actual coding do we do in a day?”
Then someone joked, “Does waiting for builds count?” — and that changed the direction of the conversation completely. We opened our laptops, started the stopwatch, and decided to measure it.
Here’s what we found:
- Minimum:Â 107 seconds
- Maximum:Â 274 seconds
- Average:Â No one made it under one minute.
At that moment, I realized something important:
As developers, performance optimization isn’t just for our apps — it’s also for our development workflow.
đź§ What Should I Optimize?
The main factors affecting Xcode build time are:
- Number of files
- Dependencies
- Derived Data
The first two usually require mid-to-long-term improvements.
Derived Data, however, has the potential for instant gains.
That’s why I wrote two scripts — one reveals the harsh truth (the clean build baseline), and the other measures the power of caching.
📦 What Is Derived Data?
Derived Data is the place where Xcode stores all the temporary build artifacts, module caches, indexing data, and compiled outputs generated during the build process.
In short:
It’s Xcode’s hidden cache system that makes your builds faster.
Benefits:
✅ Enables incremental builds — only changed files are recompiled
âś… Speeds up code completion and indexing
âś… Avoids recompiling the same frameworks repeatedly
âś… Caches test results and simulator data
When Should You Clean It?
🧨 When Xcode starts throwing weird errors
🧨 When build times suddenly increase
🧨 When you change project targets or structure
🧨 When upgrading to a new Xcode version
Comand
rm -rf ~/Library/Developer/Xcode/DerivedData
Location
cd ~/Library/Developer/Xcode/DerivedData/
# MyApp-efzslzqfawpjxgfzkkhffqvrrkbe/
🧪 The Real Test: Measuring the “With vs Without Cache” Difference
I wanted to see the real impact of Derived Data — not just in theory, but in numbers.
In other words, I wanted to measure the difference between a clean buildand a cached build.
So I wrote my first script:
🔹 1. build_measure.sh
This script performs a clean build, meaning it completely clears the cache before starting.
Goal: to measure exactly how long it takes Xcode to build everything from scratch.
#!/bin/bash
set -euo pipefail
SCHEME="YourApp"
CONFIGURATION="Debug"
for run in 1 2 3; do
echo "Baseline run #$run"
DD=$(mktemp -d /tmp/DerivedData.baseline.XXXXXX)
start=$(date +%s)
xcodebuild -project YourApp.xcodeproj \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-derivedDataPath "$DD" \
clean build
end=$(date +%s)
dur=$((end-start))
echo "$(date): baseline ${CONFIGURATION} - ${dur}s" | tee -a build_times.log
rm -rf "$DD"
done
What does this script do?
- set -euo pipefail: Stops execution on errors, prevents use of undefined variables, and catches pipe failures — ensures safe, predictable execution.
- mktemp -d:Â Creates a new, empty DerivedData folder for each run (isolation).
- -derivedDataPath “$DD”: Forces Xcode to use that empty folder, ensuring a true clean build.
- date +%s:Â Captures start and end timestamps to calculate total build time in seconds.
- Logging: Appends each run’s duration to theÂ
build_times.log file. - Cleanup: Deletes the temporary DerivedData folder at the end of each run.
When this script runs, Xcode rebuilds everything from scratch — the result: your true baseline build time.
🔹 2. build_with_cache.sh
The second script measures the real-world cached build scenario.
It doesn’t perform a clean build — instead, it preserves and reuses the cache, using a unique CACHE_KEY to version and isolate each environment.
#!/bin/bash
set -euo pipefail
SCHEME="YourApp"
CONFIGURATION="Debug"
CACHE_ROOT="$HOME/.cache/xcode/DerivedDataCache"
XCODE_VER=$(xcodebuild -version | tr '\n' ' ' | sed 's/ */ /g')
SDK_VER=$(xcrun --sdk iphoneos --show-sdk-version 2>/dev/null || echo "NA")
SPM_LOCK_HASH=$(test -f Package.resolved && shasum Package.resolved | awk '{print $1}' || echo "no-spm")
PODS_LOCK_HASH=$(test -f Podfile.lock && shasum Podfile.lock | awk '{print $1}' || echo "no-pods")
KEY=$(printf "%s|ios%s|%s|%s|%s" "$XCODE_VER" "$SDK_VER" "$SPM_LOCK_HASH" "$PODS_LOCK_HASH" "$CONFIGURATION" | shasum | awk '{print $1}')
DERIVED_DATA="${CACHE_ROOT}/${KEY}"
mkdir -p "$DERIVED_DATA"
for run in 1 2 3; do
echo "Cached run #$run"
start=$(date +%s)
xcodebuild -project YourApp.xcodeproj \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-derivedDataPath "$DERIVED_DATA" \
build
end=$(date +%s)
dur=$((end-start))
echo "$(date): cached ${CONFIGURATION} - ${dur}s" | tee -a build_times.log
done
echo "DerivedData path: $DERIVED_DATA"
What does this script do?
- set -euo pipefail: Stops execution on errors, prevents undefined variables, and catches pipe failures — ensures reliable execution.
- CACHE_ROOT:Â Defines the central directory where all caches are stored (
~/.cache/xcode/DerivedDataCache). - XCODE_VER / SDK_VER:Â Captures the current Xcode and iOS SDK versions (if either changes, the cache key will too).
- SPM_LOCK_HASH / PODS_LOCK_HASH:Â Calculates hashes ofÂ
Package.resolved and/orÂPodfile.lock — whenever dependencies change, the cache key changes automatically. - KEY: Combines Xcode, SDK, dependency hashes, and build configuration into a single SHA-1 hash — this becomes the unique cache identifier.
- DERIVED_DATA: Points to the DerivedData folder associated with that specific key — each environment gets its own isolated cache.
- mkdir -p: Creates the cache folder if it doesn’t already exist.
- for 1..3: Runs the build three times — the first warms up the cache, while the second and third reflect the true incremental build performance.
- -derivedDataPath “$DERIVED_DATA”: Reuses the same cache path across all runs; no clean build is performed.
- date +%s & log:Â Measures build time and appends the duration toÂ
build_times.log. - Output:Â Prints the DerivedData path used at the end of execution for reference.
The goal here is to quantify the performance difference under the same environment.
If a clean build takes 180 seconds and the cached build finishes in 45 seconds,
that’s roughly a 75% speed improvement — measurable, repeatable, and real.
My Results


As you can see, the baseline clean build took 133 seconds, but after implementing the cache structure, it dropped to just 13 seconds.
A few lines of Bash code ended up saving minutes of build time.
Instead of randomly deleting Derived Data, versioning it intelligently with a key-based system brought both speed and stability.
With these two scripts, build times are no longer something we “feel”
they’re now a measurable, trackable performance metric.
Bir yanıt yazın