10 octombrie 2026 · 12 min. de citit
Cum ștergem Xcode DerivedData și de ce ocupă zeci de gigabyți pe Mac
Când spațiul liber de pe Mac începe să se termine, mai devreme sau mai târziu te uiți spre directorul Xcode. Dezvoltatorii iOS adună adesea zeci de gigabyți de DerivedData: cache-uri, indexuri, produse de build, dependențe Swift Package Manager.
Ștergerea totală printr-o singură comandă este simplă. Doar că, imediat după, Xcode trebuie să recompileze dependențele, să refacă indexul și să reconstruiască cache-urile, iar prima compilare durează vizibil mai mult decât de obicei. De aceea merită să înțelegem mai întâi ce anume a crescut și abia apoi să decidem cum curățăm.
Răspunsul scurt: pentru majoritatea situațiilor, mai simplu este să folosești o unealtă gratuită dedicată, de exemplu DevCleaner for Xcode din Mac App Store sau una dintre utilitățile open-source enumerate mai jos. Câteva clickuri, fără Terminal, recuperează, de obicei, 10–50 GB într-o singură scanare. Dar dacă vrei să înțelegi ce se șterge sau să integrezi verificarea dimensiunii în fluxul tău de lucru și în CI, un mic script zsh propriu este o alegere rezonabilă.
În articol trecem prin fiecare pas: ce se află în DerivedData, cum măsurăm, ce metode de curățare există, iar la final o secțiune dedicată scriptului meu xcode-dd ca exemplu de abordare DIY.
La ce folosește DerivedData
DerivedData este directorul de lucru al Xcode. Aici sunt păstrate produsele și fișierele intermediare ale compilării, indexul codului sursă, datele Swift Package Manager, jurnalele și diferite cache-uri.
Locația implicită este:
~/Library/Developer/Xcode/DerivedData
Poți verifica sau schimba această locație din Xcode → Settings → Locations → Derived Data.
De exemplu, dacă modifici un singur fișier Swift, sistemul de build poate reutiliza rezultatele anterioare și recompila numai părțile afectate. Indexul permite navigarea rapidă între simboluri și căutarea utilizărilor lor. Toate acestea economisesc timp, dar ocupă spațiu pe disc.
În general, ștergerea DerivedData nu afectează codul sursă. Xcode recreează datele necesare, însă prima compilare și reindexarea pot dura mai mult.
Ce găsim în interior
Structura directorului principal arată, de obicei, astfel:
DerivedData/
├── MyApp-hglrthxzzosvzfecqgfkqfqcjpvj/
├── AnotherApp-fgqhqozcpoxefycwqgslpklsgkzi/
├── CompilationCache.noindex/
├── ModuleCache.noindex/
└── SDKStatCaches.noindex/
Fiecare proiect poate avea propriul director, care conține:
MyApp-hglrthxzzosvzfecqgfkqfqcjpvj/
├── Build/
│ ├── Intermediates.noindex/
│ └── Products/
├── Index.noindex/
├── SourcePackages/
├── Logs/
└── info.plist
Build conține fișierele intermediare și produsele compilării.
Index.noindex conține date folosite pentru navigarea și căutarea în cod.
SourcePackages este legat de Swift Package Manager și poate ocupa foarte mult spațiu atunci când proiectul are multe dependențe.
Logs păstrează informații despre build-uri și teste.
Fișierul info.plist este util pentru identificarea proiectului. Cheia WorkspacePath indică fișierul .xcodeproj sau .xcworkspace asociat directorului. O poți citi astfel:
/usr/libexec/PlistBuddy -c 'Print :WorkspacePath' \
~/Library/Developer/Xcode/DerivedData/<project>/info.plist
Directoarele CompilationCache.noindex, ModuleCache.noindex și SDKStatCaches.noindex, aflate la nivelul principal, sunt cache-uri comune. Ele nu aparțin unui singur proiect, așa că ștergerea unui director de proiect nu le elimină automat.
De ce ajunge să ocupe zeci de gigabyți
Configurațiile de build, destinațiile diferite, dependențele și proiectele la care lucrăm lasă în urmă date. În plus, ștergerea unui checkout nu înseamnă neapărat că Xcode elimină și DerivedData asociat. Situația este și mai vizibilă atunci când folosim Git worktrees create temporar de coding agents.
Să luăm un exemplu ilustrativ, cu valori rotunjite, pentru un director de 15.7 GB:
10.5 GB Test-civxctdjvxfxvzewaauzvbgmsmgg
3.5 GB CompilationCache.noindex
1.6 GB ModuleCache.noindex
11.0 MB SDKStatCaches.noindex
În cel mai mare proiect, distribuția ar putea fi:
SourcePackages 7.6 GB
Build 2.7 GB
Index.noindex 135.0 MB
Logs 2.5 MB
Observăm că dependențele ocupă mai mult decât rezultatele compilării. Tocmai de aceea merită să verificăm conținutul înainte de a șterge totul.
Cum aflăm ce ocupă spațiul
Mai întâi, verificăm dimensiunea totală:
du -sh ~/Library/Developer/Xcode/DerivedData
Apoi listăm directoarele în ordinea dimensiunii:
du -sh ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null | sort -hr
Pentru un singur proiect:
du -hd 1 ~/Library/Developer/Xcode/DerivedData/<project> 2>/dev/null | sort -hr
Dacă ai configurat o altă locație pentru DerivedData, folosește acea cale.
Metode de curățare a DerivedData
Există mai multe variante, fiecare cu propriile avantaje.
1. Xcode Settings și Finder
Închide Xcode, deschide directorul DerivedData din Xcode → Settings → Locations (lângă cale există o săgeată mică ce deschide directorul în Finder) și elimină numai directoarele de care nu mai ai nevoie.
Este varianta cea mai sigură: vezi dimensiunea fiecărui director și ștergi doar ceea ce vrei.
2. Terminal
Din Terminal, se folosește adesea:
rm -rf ~/Library/Developer/Xcode/DerivedData/*
Atenție: rm -rf șterge direct fișierele, fără să le trimită în Coș, iar * nu include elementele ascunse. Verifică atent calea înainte de execuție.
Dacă problema ține de rezultatele compilării, încearcă mai întâi Product → Clean Build Folder. Această comandă nu este echivalentă cu ștergerea completă a DerivedData: nu resetează integral indexul, starea pachetelor și cache-urile comune. Dar pentru erori de build este adesea suficientă.
Nu toate datele Xcode se află aici. Arhivele sunt păstrate în ~/Library/Developer/Xcode/Archives, iar datele simulatoarelor în ~/Library/Developer/CoreSimulator. Dacă acelea umplu discul, curățarea DerivedData nu va schimba mare lucru.
3. Xcode 27: Product → Delete Derived Data
Xcode 27 a introdus, în sfârșit, comanda Product → Delete Derived Data (detalii), care permite ștergerea datelor proiectului curent direct din IDE, fără să cauți directorul.
Este o soluție mai precisă decât eliminarea întregului director. Totuși, nu are sens să ștergem cache-urile înainte de fiecare build: de cele mai multe ori, ele tocmai accelerează compilarea. Dacă aceeași eroare dispare mereu doar după o ștergere, caută cauza reală.
4. Unelte gratuite de la terți
Dacă nu vrei să întreții propriul script sau să folosești Terminal, există soluții la cheie. Toate cele enumerate mai jos sunt gratuite: fie freeware în Mac App Store, fie open source pe GitHub.
DevCleaner for Xcode: probabil cea mai populară soluție dedicată. Utilitate GUI din Mac App Store, scanează întregul ~/Library/Developer: DerivedData, simulatoare vechi, iOS Device Support, cache-uri de documentație. Freeware cu tip jar, recuperează, de obicei, 10–50 GB într-o scanare. Pentru majoritatea utilizatorilor este soluția „la cheie”, scriptul nu este necesar.
XcodeClean: utilitate open-source din bara de meniu pentru operații obișnuite de curățare Xcode, inclusiv DerivedData și cache-urile de pachete. Mie mi-au plăcut cel mai mult tocmai abordarea din bara de meniu și interfața simplă: butonul de curățare este la un click distanță, fără ecrane suplimentare.
mac-dev-clean: CLI open-source pentru o curățare mai amplă a mediului de dezvoltare. Util dacă spațiul este consumat de mai multe tipuri de cache, nu doar Xcode.
ClearDisk: GUI open-source pentru analiza și curățarea cache-urilor de dezvoltare.
Pentru cei care lucrează deja cu coding agents, Xcode Disk Cleanup Agent Skill de Antoine van der Lee (open source). În loc de o comandă fixă, poți cere agentului să analizeze spațiul, să propună candidați pentru ștergere și să discute ce date nu mai sunt necesare. Analiza automată nu înlocuiește verificarea manuală a căilor înainte de ștergere.
5. Scriptul tău zsh xcode-dd
Dacă DevCleaner există deja, de ce să scrii un script? Întrebare bună: pentru majoritatea nu are rost. Dacă vrei doar să recuperezi 30 GB o dată, utilitatea GUI este suficientă. Un script personal are sens când:
- vrei să adaugi verificări de dimensiune în fluxul tău de lucru (un fel de obicei la nivel de
git status, aliasat în shell), - ai nevoie să-l rulezi din CI, hook-uri pre-commit sau scenarii de coding agent, fără GUI,
- vrei reguli personalizate pentru căi protejate, filtrare pe proiecte sau un format propriu de ieșire,
- tot ce îți trebuie este deja în zsh și
du, iar o aplicație nouă pare prea mult.
Dacă te regăsești, mai jos este scriptul.
xcode-dd: o singură comandă pentru o imagine de ansamblu
xcode-dd are trei moduri:
| Comandă | Ce face |
|---|---|
xcode-dd sau xcode-dd status | Afișează dimensiunea totală, spațiul liber și cele mai mari directoare |
xcode-dd details | Afișează calea workspace-ului și dimensiunile Build, Index.noindex, SourcePackages, Logs |
xcode-dd clean | Șterge conținutul DerivedData după confirmare |
Un exemplu de rezultat:
Xcode DerivedData
Path: /Users/eugene/Library/Developer/Xcode/DerivedData
Total: 15.7 GB
Disk: 460.4 GB
Free: 99.2 GB
Of disk: 3.4%
Of free: 15.8%
Largest:
10.5 GB Test-civxctdjvxfxvzewaauzvbgmsmgg
3.5 GB CompilationCache.noindex
1.6 GB ModuleCache.noindex
11.0 MB SDKStatCaches.noindex
xcode-dd details adaugă detalii pe fiecare proiect:
Test-civxctdjvxfxvzewaauzvbgmsmgg 10.5 GB
Workspace /Users/eugene/Test-ios/Test/Test.xcodeproj
Build 2.7 GB
Index.noindex 135.0 MB
SourcePackages 7.6 GB
Logs 2.5 MB
Nucleul într-un singur fragment
Ideea este simplă: du măsoară, sort și awk formatează. Mai jos este un status minimal care face exact acest lucru:
#!/bin/zsh
DD="$HOME/Library/Developer/Xcode/DerivedData"
echo "Total:"
du -sh "$DD" 2>/dev/null | awk '{print $1}'
echo
echo "Largest:"
du -sk "$DD"/* 2>/dev/null \
| sort -rn \
| head -10 \
| awk '{ kb=$1; $1=""; printf "%7.1f GB %s\n", kb/1048576, substr($0, 2) }'
Atât este suficient pentru a vedea, dintr-o singură comandă, ce s-a mărit.
Varianta completă adaugă: analiza fiecărui proiect prin WorkspacePath din info.plist (modul details), confirmare și așteptare prin AppleScript pentru închiderea Xcode (modul clean), verificarea căii împotriva unei liste de directoare protejate înainte de rm -rf și un helper comun human_size pentru valori lizibile.
Valoarea Freed din varianta completă reprezintă diferența dintre dimensiunile măsurate, nu neapărat creșterea exactă a spațiului liber pe APFS.
Instalare
Salvează codul de mai jos într-un fișier numit xcode-dd și execută:
chmod +x xcode-dd
mkdir -p ~/.local/bin
mv xcode-dd ~/.local/bin/
Dacă ~/.local/bin nu se află în PATH, adaugă în ~/.zshrc:
export PATH="$HOME/.local/bin:$PATH"
După ce deschizi un Terminal nou, poți rula xcode-dd, xcode-dd details și xcode-dd clean. Pentru o locație personalizată, setează XCODE_DD_PATH.
Codul sursă complet
Testează modul
cleanpe un director temporar înainte să îl folosești cu date reale. Scriptul nu a fost rulat pe macOS în timpul pregătirii acestui articol.
Deschide codul sursă complet al xcode-dd
#!/bin/zsh
# xcode-dd: inspect and clean Xcode DerivedData.
# Usage: xcode-dd [status|details|clean]
# XCODE_DD_PATH overrides the default path.
set -u
DEFAULT_DD="$HOME/Library/Developer/Xcode/DerivedData"
DD="${XCODE_DD_PATH:-$DEFAULT_DD}"
FORBIDDEN_PATHS=("/" "/Users" "${HOME:A}" "${HOME:A}/Library" "${HOME:A}/Library/Developer" "${HOME:A}/Library/Developer/Xcode")
human_size() {
local kb="${1:-0}"
awk -v kb="$kb" 'BEGIN { if (kb >= 1048576) printf "%.1f GB", kb / 1048576; else if (kb >= 1024) printf "%.1f MB", kb / 1024; else printf "%.0f KB", kb }'
}
directory_size_kb() {
local target="$1"
[[ -e "$target" ]] || { echo 0; return; }
du -sk "$target" 2>/dev/null | awk -F '\t' '{print $1}'
}
scan_dd() {
SCAN_ENTRIES=()
SCAN_TOTAL_KB=0
local -a top_level
top_level=("$DD"/*(ND))
(( ${#top_level[@]} )) || return 0
local kb name
while IFS=$'\t' read -r kb name; do
[[ -n "$kb" ]] || continue
SCAN_ENTRIES+=("$kb"$'\t'"${name:t}")
(( SCAN_TOTAL_KB += kb ))
done < <(du -sk -- "${top_level[@]}" 2>/dev/null)
}
sorted_scan_entries() {
local entry
for entry in "${SCAN_ENTRIES[@]}"; do
print -r -- "$entry"
done | sort -t $'\t' -k1,1nr
}
show_status() {
[[ -d "$DD" ]] || { echo "DerivedData directory does not exist: $DD"; return 0; }
scan_dd
print_status_from_scan
}
print_status_from_scan() {
local disk_info total_kb free_kb
disk_info=$(df -k "$DD" | tail -1)
total_kb=$(echo "$disk_info" | awk '{print $2}')
free_kb=$(echo "$disk_info" | awk '{print $4}')
echo "Xcode DerivedData"
echo
printf "%-14s %s\n" "Path:" "$DD"
printf "%-14s %s\n" "Total:" "$(human_size "$SCAN_TOTAL_KB")"
printf "%-14s %s\n" "Disk:" "$(human_size "$total_kb")"
printf "%-14s %s\n" "Free:" "$(human_size "$free_kb")"
if (( total_kb > 0 )); then
awk -v dd="$SCAN_TOTAL_KB" -v total="$total_kb" 'BEGIN { printf "%-14s %.1f%%\n", "Of disk:", dd * 100 / total }'
fi
if (( free_kb > 0 )); then
awk -v dd="$SCAN_TOTAL_KB" -v free="$free_kb" 'BEGIN { printf "%-14s %.1f%%\n", "Of free:", dd * 100 / free }'
fi
echo
echo "Largest:"
(( ${#SCAN_ENTRIES[@]} )) || { echo " No DerivedData directories."; return 0; }
sorted_scan_entries | while IFS=$'\t' read -r kb name; do
printf "%12s %s\n" "$(human_size "$kb")" "$name"
done
}
workspace_path_for() {
local plist="$1/info.plist"
[[ -f "$plist" ]] || return 1
/usr/libexec/PlistBuddy -c "Print :WorkspacePath" "$plist" 2>/dev/null
}
show_details() {
[[ -d "$DD" ]] || { echo "DerivedData directory does not exist: $DD"; return 0; }
scan_dd
(( ${#SCAN_ENTRIES[@]} )) || { echo "No DerivedData directories."; return 0; }
sorted_scan_entries | while IFS=$'\t' read -r kb name; do
local dir="$DD/$name"
echo
printf "%-45s %s\n" "$name" "$(human_size "$kb")"
if [[ -d "$dir" ]]; then
local workspace=""
workspace=$(workspace_path_for "$dir")
[[ -z "$workspace" ]] || printf " %-12s %s\n" "Workspace" "$workspace"
local -a components=(Build Index.noindex SourcePackages Logs)
local component="" component_path="" component_kb=""
for component in "${components[@]}"; do
component_path="$dir/$component"
if [[ -e "$component_path" ]]; then
component_kb=$(directory_size_kb "$component_path")
printf " %-25s %s\n" "$component" "$(human_size "$component_kb")"
fi
done
fi
done
}
wait_for_xcode_to_quit() {
local timeout=30 elapsed=0
pgrep -x Xcode >/dev/null || return 0
echo
echo "Xcode is running. Asking it to quit..."
osascript -e 'tell application "Xcode" to quit' >/dev/null || return 1
while pgrep -x Xcode >/dev/null; do
sleep 1
(( elapsed++ ))
if (( elapsed >= timeout )); then
echo "Xcode did not quit after ${timeout}s. Nothing was deleted."
return 1
fi
done
echo "Xcode closed."
}
assert_safe_dd_path() {
local resolved="${DD:A}" forbidden
if [[ -z "$resolved" || "$resolved" == "/" ]]; then
echo "Refusing to clean unsafe path: ${resolved:-${DD:-<empty>}}"
return 1
fi
for forbidden in "${FORBIDDEN_PATHS[@]}"; do
if [[ "$resolved" == "$forbidden" ]]; then
echo "Refusing to clean protected path: $resolved"
return 1
fi
done
}
clean_derived_data() {
[[ -d "$DD" ]] || { echo "DerivedData directory does not exist: $DD"; return 0; }
assert_safe_dd_path || return 1
scan_dd
local before=$SCAN_TOTAL_KB
print_status_from_scan
echo
echo "This will delete the CONTENTS of:"
echo "${DD:A}"
echo
printf "Potential cleanup: %s\n" "$(human_size "$before")"
echo
local answer=""
read "answer?Delete all DerivedData? [y/N] "
[[ "$answer" =~ ^[Yy]$ ]] || { echo "Cancelled."; return 0; }
wait_for_xcode_to_quit || return 1
assert_safe_dd_path || return 1
echo
echo "Deleting DerivedData..."
rm -rf -- "$DD"/*(ND) || return 1
local after freed
after=$(directory_size_kb "$DD")
freed=$(( before - after ))
(( freed < 0 )) && freed=0
echo
printf "%-10s %s\n" "Before:" "$(human_size "$before")"
printf "%-10s %s\n" "After:" "$(human_size "$after")"
printf "%-10s %s\n" "Freed:" "$(human_size "$freed")"
}
case "${1:-status}" in
status) show_status ;;
details) show_details ;;
clean) clean_derived_data ;;
*) echo "Usage: xcode-dd [status|details|clean]"; exit 1 ;;
esac
Git worktrees și CI
Coding agents folosesc adesea Git worktrees temporare, de exemplu:
/Users/eugene/Test-ios
/Users/eugene/.codex/worktrees/5198/Test-ios
Dacă un checkout a fost deschis sau compilat în Xcode, datele sale pot rămâne chiar și după ștergerea worktree-ului. Nu orice worktree primește neapărat un director DerivedData separat; WorkspacePath ajută la identificarea legăturii.
În CI, putem specifica explicit locația:
xcodebuild \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-derivedDataPath /tmp/MyApp-DerivedData \
build
Păstrarea cache-ului între job-uri sau ștergerea lui după fiecare build depinde de strategia de izolare și de performanță a proiectului.
În loc de concluzie
DerivedData ocupă spațiu tocmai pentru că Xcode păstrează rezultate pe care le poate reutiliza. Înainte de o curățare completă, merită să vedem dacă spațiul este consumat de Build, SourcePackages, index sau cache-urile comune. Pentru o verificare rapidă ajunge du, pentru un singur proiect Product → Delete Derived Data din Xcode 27 este cea mai scurtă cale, iar pentru o imagine regulată alege o utilitate gratuită sau propriul xcode-dd. O curățare totală păstreaz-o pentru situațiile în care chiar este necesară.