Um zwei Uhr nachts schlägt die CI-Pipeline Alarm: Weniger als 2 GB frei auf dem Build-Knoten, xcodebuild bricht ab. Im Terminal zeigt df -h: Die 256-GB-SSD ist voll — ~/Library/Developer allein belegt 180 GB, davon über 90 GB ein Verzeichnis namens DerivedData.
Das ist kein Einzelfall. Auf einem M4 Mac Mini, der acht Monate ohne Bereinigung lief, sammelt sich DerivedData im Schnitt 40–100 GB an; mit Simulator-Images und Gerätesymbolen sind über 150 GB in ~/Library/Developer keine Seltenheit. Dieser Artikel führt Sie in fünf Schritten von Ursachenanalyse → schnelle Lokalisierung → gezielte Bereinigung → CI-Schutz → Automatisierung — ein Handbuch zum direkten Mitnehmen.
Nach dem Lesen haben Sie: Nutzen und Sicherheitsbewertung der DerivedData-Unterverzeichnisse, einen Befehl für die größten Speicherfresser, Bereinigungsskripte für CI und lokal sowie ein Automatisierungskonzept, damit Festplattenalarme nicht mehr auftreten.
Quick Answer: die drei häufigsten Fragen
| Frage | Direkte Antwort | Risiko |
|---|---|---|
| Kann man DerivedData einfach löschen? | rm -rf ~/Library/Developer/Xcode/DerivedData/* ist völlig sicher |
Nächster Build wird vollständig, ca. 2–5 Min. länger |
| Kann man Archives löschen? | Versionen ohne aktive Nutzer: ja; laufende Versionen: dSYM behalten | Alte Crash-Logs lassen sich nicht mehr symbolisieren |
| CI-Knoten fast voll — schnelle Hilfe? | DerivedData → unavailable-Simulatoren → alte DeviceSupport, der Reihe nach | Index muss neu aufgebaut werden, ca. 3 Min. langsamer |
Was steckt in DerivedData?
Viele Entwickler wissen nur: „DerivedData leeren behebt mysteriöse Probleme“ — aber nicht, was drin liegt. Folge: Falsches wird gelöscht oder Nötiges bleibt, bis die Platte wieder voll ist. Die wichtigsten Pfade unter ~/Library/Developer:
| Pfad | Inhalt | Typische Größe | Sicher löschen? |
|---|---|---|---|
Xcode/DerivedData |
Build-Artefakte, Index, Logs, Swift-Modul-Cache | 5–60 GB | ✅ Völlig sicher, wird beim nächsten Build neu erzeugt |
Xcode/Archives |
Release-Builds (.xcarchive) inkl. dSYM | 2–30 GB | ⚠️ Alte Versionen ok, laufende: dSYM behalten |
Xcode/iOS DeviceSupport |
Debug-Symbole beim Anschließen eines Geräts | 1–3 GB / iOS-Version | ✅ Alte Versionen ok, werden bei Bedarf neu geladen |
CoreSimulator/Profiles/Runtimes |
Simulator-Runtime-Images | 5–10 GB / iOS-Version | ⚠️ Ungenutzte Versionen löschbar, Download nötig |
CoreSimulator/Devices |
Daten angelegter Simulator-Instanzen | 1–15 GB | ✅ Status „unavailable“ sicher löschbar |
Caches/org.swift.swiftpm |
SPM-Paket-Cache | 1–8 GB | ✅ Löschbar, resolve baut neu auf |
Caches/com.apple.dt.Xcode |
Interner Xcode-Cache (Preview, IB) | 0,5–3 GB | ✅ Löschbar |
DerivedData-Unterverzeichnisse
Jedes Projekt hat ein eigenes Unterverzeichnis {Projektname}-{Hash}/:
MyApp-abcdefghijklmn/
├── Build/
│ ├── Products/ # 编译产物(.app, .framework, .o 文件)
│ └── Intermediates.noindex/ # 中间文件(.o, .d 依赖图)
├── Index.noindex/ # SourceKit 索引(代码跳转、自动补全)
├── Logs/ # 构建日志(xcodebuild -resultBundlePath 也在这里)
└── info.plist # 项目路径记录
Build/Intermediates.noindex ist meist am größten — ein mittleres Projekt mit 200 Swift-Dateien überschreitet hier leicht 2 GB; mit Index.noindex sind 5–10 GB pro Projekt üblich.
Fünf Schritte zum Speicherfresser
Nicht blind aufräumen — 3 Minuten Analyse sparen Zeit. Dieser Ablauf wurde auf einem 256-GB M4 Mac Mini getestet und findet die Top-5-Verzeichnisse in unter 2 Minuten.
Schritt 1: Gesamtauslastung prüfen
# 查看全盘使用率
df -h /
# 快速定位前 10 大目录(整个根盘)
sudo du -sh /* 2>/dev/null | sort -rh | head -10
Schritt 2: Developer-Verzeichnis aufschlüsseln
du -sh ~/Library/Developer/Xcode/* 2>/dev/null | sort -rh
du -sh ~/Library/Developer/CoreSimulator/Profiles/Runtimes/* 2>/dev/null | sort -rh
du -sh ~/Library/Developer/Xcode/iOS\ DeviceSupport/* 2>/dev/null | sort -rh
Beispielausgabe (CI-Knoten nach 8 Monaten):
91G /Users/ci/Library/Developer/Xcode/DerivedData
28G /Users/ci/Library/Developer/CoreSimulator
18G /Users/ci/Library/Developer/Xcode/iOS DeviceSupport
12G /Users/ci/Library/Developer/Xcode/Archives
6G /Users/ci/Library/Developer/Xcode/Caches
Schritt 3: Größte DerivedData-Projekte
du -sh ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null | sort -rh | head -10
Schritt 4: Simulator-Belegung und Status
# 列出所有模拟器及状态
xcrun simctl list devices
# 只看"已不可用"(系统升级后遗留)
xcrun simctl list devices | grep -i "unavailable"
Schritt 5: DeviceSupport-Versionen
ls -lh ~/Library/Developer/Xcode/iOS\ DeviceSupport/
So sieht es aus, wenn sich Jahre an Symboldateien angesammelt haben:
drwxr-xr-x 3 dev staff 96B iOS 15.4 (19E241)
drwxr-xr-x 3 dev staff 96B iOS 16.1 (20B82)
drwxr-xr-x 3 dev staff 96B iOS 17.2 (21C62)
drwxr-xr-x 3 dev staff 96B iOS 18.3.2 (22D82)
Symbole für iOS 15 und 16 haben 2026 kaum noch Nutzen (App-Store-Minimum oft iOS 17) — sicher löschbar.
Verzeichnisstrategie: löschen, vorsichtig, behalten
Direkt löschbar (null Risiko)
# 1. 清空所有项目的编译缓存
rm -rf ~/Library/Developer/Xcode/DerivedData/*
# 2. 删除已不可用的模拟器
xcrun simctl delete unavailable
# 3. 清理 SPM 包缓存
rm -rf ~/Library/Caches/org.swift.swiftpm/*
# 4. 清理 Xcode 内部缓存
rm -rf ~/Library/Caches/com.apple.dt.Xcode/*
# 5. 清理 SwiftUI 预览缓存
rm -rf ~/Library/Developer/Xcode/UserData/Previews/*
Selektiv löschen (alt weg, neu behalten)
# 删除 2 个版本以前的设备符号(保留最新两个 iOS 版本)
# 先确认当前有哪些版本
ls ~/Library/Developer/Xcode/iOS\ DeviceSupport/
# 示例:只保留 iOS 18.x,删除更早的
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/iOS\ 15*
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/iOS\ 16*
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/iOS\ 17*
Vorsicht bei Archives
- Version aus dem App Store entfernt oder ohne aktive Nutzer
- Bei laufenden Nutzern: dSYM exportieren (Organizer → Archive rechtsklick → Im Finder zeigen → .dSYM sichern)
- dSYM zu Firebase Crashlytics / Sentry o. Ä. hochladen
- Erst nach Bestätigung des Uploads das Archive löschen
Notfall-Befehl (Platte fast voll)
In dieser Reihenfolge lassen sich meist 20–60 GB freigeben:
#!/bin/bash
# emergency-clean-xcode.sh
# 执行前确认:已无正在运行的 xcodebuild 进程
echo "=== Xcode 磁盘紧急清理 ==="
echo "[1/4] 清理 DerivedData..."
du -sh ~/Library/Developer/Xcode/DerivedData 2>/dev/null
rm -rf ~/Library/Developer/Xcode/DerivedData/*
echo "✓ DerivedData 已清空"
echo "[2/4] 删除不可用模拟器..."
xcrun simctl delete unavailable
echo "✓ 不可用模拟器已删除"
echo "[3/4] 清理 SPM 和 Xcode 缓存..."
rm -rf ~/Library/Caches/org.swift.swiftpm/*
rm -rf ~/Library/Caches/com.apple.dt.Xcode/*
echo "✓ 缓存已清理"
echo "[4/4] 清理预览缓存..."
rm -rf ~/Library/Developer/Xcode/UserData/Previews/*
echo "✓ 预览缓存已清理"
echo ""
echo "=== 清理完成,当前磁盘状态 ==="
df -h /
CI/CD: Blockaden vermeiden
Lokal reicht manuelles Aufräumen — CI-Runner laufen unbeaufsichtigt und brauchen System. Beispiel: selbst gehosteter macOS-Runner (GitHub Actions / GitLab CI), drei Ansätze.
Ansatz A: Fester DerivedData-Pfad + inkrementeller Cache
DerivedData auf einen kontrollierbaren Pfad legen und über CI-Cache zwischen Jobs teilen:
# .github/workflows/ios-build.yml 片段
- name: Build
run: |
xcodebuild \
-project MyApp.xcodeproj \
-scheme MyApp \
-configuration Release \
-derivedDataPath /tmp/derived-data \
-resultBundlePath /tmp/result.xcresult \
build
- name: Cache DerivedData
uses: actions/cache@v4
with:
path: /tmp/derived-data
key: derived-data-${{ runner.os }}-${{ hashFiles('**/Package.resolved') }}-${{ hashFiles('**/*.xcodeproj/project.pbxproj') }}
restore-keys: |
derived-data-${{ runner.os }}-
Package.resolved und project.pbxproj hashen — sonst schlagen Builds nach SPM-Updates wegen veraltetem Cache fehl.
Ansatz B: Alte Cache-Einträge vor dem Build löschen
# 在构建步骤之前:清理超过 7 天未使用的 DerivedData 子目录
- name: Clean stale DerivedData
run: |
find ~/Library/Developer/Xcode/DerivedData \
-maxdepth 1 -mindepth 1 \
-type d \
-atime +7 \
-exec rm -rf {} +
echo "DerivedData 清理完成,当前大小:"
du -sh ~/Library/Developer/Xcode/DerivedData 2>/dev/null || echo "目录为空"
Ansatz C: GitLab CI Kapazitäts-Gate
Pre-Job in .gitlab-ci.yml: bei zu wenig Platz sofort fail-fast, statt mitten im Build abzubrechen:
# .gitlab-ci.yml
check_disk:
stage: .pre
script:
- |
AVAIL=$(df -k / | awk 'NR==2 {print $4}')
THRESHOLD=$((10 * 1024 * 1024)) # 10 GB in KB
if [ "$AVAIL" -lt "$THRESHOLD" ]; then
echo "ERROR: 磁盘剩余不足 10 GB(当前: $((AVAIL/1024/1024)) GB),请先清理"
exit 1
fi
echo "磁盘检查通过,剩余: $((AVAIL/1024/1024)) GB"
tags:
- macos-runner
Vergleich der drei Ansätze
| Ansatz | Einsatz | Aufwand | Cache-Nutzung |
|---|---|---|---|
| A: Fester Pfad + Cache | Mittlere/große Projekte, viele PRs, Build-Zeit kritisch | Mittel (Cache-Action konfigurieren) | Hoch (übergreifend) |
| B: Regelmäßig alte Unterordner | Mehrere Projekte, ausreichend Platte | Gering (ein find-Befehl) | Mittel (am selben Tag) |
| C: Kapazitäts-Gate | Überall als letzte Sicherung | Minimal (5 Zeilen) | Kein Einfluss |
Empfehlung: alle drei kombinieren — A für Geschwindigkeit, B für Wachstumskontrolle, C als Netz.
Automatisierung: geplanter Job + Kapazitätswarnung
Manuelles Aufräumen versagt langfristig. Skript + macOS launchd machen aus Reaktion Vorsorge.
Vollständiges Wartungsskript
#!/bin/bash
# xcode-maintenance.sh
# 建议每周日凌晨 3 点执行
# 放置路径:~/scripts/xcode-maintenance.sh
LOGFILE=~/Library/Logs/xcode-maintenance.log
THRESHOLD_GB=20 # 当剩余磁盘低于此值时触发深度清理
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"; }
log "=== Xcode 定期维护开始 ==="
# 1. 记录清理前的磁盘状态
BEFORE=$(df -h / | awk 'NR==2 {print $4}')
log "清理前磁盘可用: $BEFORE"
# 2. 删除 14 天未访问的 DerivedData 子目录
log "清理 14 天以上未使用的 DerivedData..."
find ~/Library/Developer/Xcode/DerivedData \
-maxdepth 1 -mindepth 1 -type d -atime +14 \
-exec rm -rf {} + 2>/dev/null
log "✓ DerivedData 老旧条目已清理"
# 3. 删除不可用模拟器
log "清理不可用模拟器..."
xcrun simctl delete unavailable 2>/dev/null
log "✓ 不可用模拟器已删除"
# 4. 清理 SPM 缓存(超过 30 天)
find ~/Library/Caches/org.swift.swiftpm -maxdepth 2 \
-type f -atime +30 -delete 2>/dev/null
log "✓ 老旧 SPM 缓存已清理"
# 5. 检查剩余空间,若低于阈值触发深度清理
AVAIL_GB=$(df -g / | awk 'NR==2 {print $4}')
if [ "$AVAIL_GB" -lt "$THRESHOLD_GB" ]; then
log "⚠️ 磁盘剩余 ${AVAIL_GB}GB < ${THRESHOLD_GB}GB,触发深度清理"
rm -rf ~/Library/Developer/Xcode/DerivedData/*
rm -rf ~/Library/Caches/com.apple.dt.Xcode/*
rm -rf ~/Library/Developer/Xcode/UserData/Previews/*
log "✓ 深度清理完成"
fi
AFTER=$(df -h / | awk 'NR==2 {print $4}')
log "清理后磁盘可用: $AFTER"
log "=== 维护完成 ==="
Als launchd-Job registrieren
Wöchentlich Sonntag 3:00 Uhr:
# 1. 给脚本加执行权限
chmod +x ~/scripts/xcode-maintenance.sh
# 2. 创建 launchd plist
cat > ~/Library/LaunchAgents/com.myteam.xcode-maintenance.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myteam.xcode-maintenance</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/YOUR_USER/scripts/xcode-maintenance.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key><integer>0</integer>
<key>Hour</key><integer>3</integer>
<key>Minute</key><integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/xcode-maintenance-stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/xcode-maintenance-stderr.log</string>
</dict>
</plist>
EOF
# 3. 加载
launchctl load ~/Library/LaunchAgents/com.myteam.xcode-maintenance.plist
echo "定时任务已注册"
Cloud-Mac: Festplattenproblem von der Wurzel
Alles oben mildert nur begrenzten Speicher. Wer CI-Infrastruktur prüft, sollte dedizierte Cloud Mac Mini statt lokaler Knoten erwägen.
In der Cloud gibt es andere Hebel:
- Erweiterte SSD: Macstripe M4 Mac Mini mit mehr Speicher — oft einfacher als endloses Aufräumen
- Periodischer Reset: Monatsknoten nach Laufzeit neu provisionieren — DerivedData von null, kein Skript nötig
- Bedarfsskalierung: Release-Sprint mehr Knoten, Flaute weniger — kein Dauerbezug für Spitzenlast
- SSH-Debugging: Bei mysteriösen Builds
ssh runner@your-node— bequemer als Remote auf Hardware
Fallbeispiel: Mobile-Team ersetzte drei lokale Mac Mini durch Macstripe-Cloud — ca. 8–12 h/Monat weniger Ops (Platte, Xcode-Updates, Runner-Fehler). Bei Ingenieursstundensatz deckt das einen großen Teil der Miete.
Für GitHub Actions: auch iOS nur mit Windows entwickeln — kein Mac mehr nötig? — vollständiger Plattformvergleich beim iOS-Build.
FAQ
F: Beeinträchtigt Löschen von DerivedData Quellcode oder Releases?
Nein. DerivedData enthält nur Build-Artefakte, SourceKit-Index und Logs — getrennt von Quellcode, Git, Provisioning Profile und App Store Connect. Xcode baut beim nächsten Mal neu auf; Preis: Vollbuild statt inkrementell, mittleres Projekt ca. 2–5 Min. mehr.
F: Archives beliebig löschen?
Nicht beliebig. .xcarchive enthält dSYM für Crash-Symbolisierung. Vorher prüfen: Version im Store ohne Nutzer, oder dSYM bei Crashlytics/Sentry. Letzte drei Releases behalten, ältere nach dSYM-Export löschen.
F: CI baut jedes Mal vollständig — Cache möglich?
Ja. -derivedDataPath /tmp/derived-data an xcodebuild, plus actions/cache oder GitLab cache:. Key mit Package.resolved und project.pbxproj. Bei vielen Swift Packages spart inkrementell 40–60 % Zeit.
F: xcodebuild scheitert wegen voller Platte — schnelle Recovery?
Der Reihe nach: ① rm -rf ~/Library/Developer/Xcode/DerivedData/* (10–60 GB); ② xcrun simctl delete unavailable (5–20 GB); ③ DeviceSupport älter als zwei Versionen (5–15 GB). Meist reicht Schritt ①.
F: „Product > Clean Build Folder“ vs. DerivedData per CLI?
Clean Build Folder nur für das geöffnete Projekt — andere Projekte, Simulator und DeviceSupport bleiben. rm -rf ~/Library/Developer/Xcode/DerivedData/* räumt alle Projekte. CI: CLI; lokaler Alltag: Clean Build Folder präziser.
F: DerivedData auf CI-Runner wächst sehr schnell?
Oft: mehrere Schemes/Branches erzeugen jeweils neue Unterordner. ① -derivedDataPath auf einen Pfad; ② find ... -atime +7 -exec rm -rf {} +; ③ größere SSD oder Macstripe Cloud mit Erweiterung prüfen.
Fazit
DerivedData-Blockaden treffen fast jeden iOS-Entwickler — systematisch lösbar, nicht als „Hexerei“ jedes Mal panisch aufräumen.
- Ursache: DerivedData ist reiner Cache; Archives mit dSYM vorsichtig behandeln
- Diagnose:
du -sh ~/Library/Developer/Xcode/* | sort -rhfindet den Übeltäter - Gezielt: DerivedData weg, DeviceSupport alt weg/neu da, Archives erst dSYM dann löschen
- CI:
-derivedDataPath+ Cache + Kapazitäts-Gate - Automatisierung: launchd + Wartungsskript, wöchentlich
- Grundlegend: Cloud-Mac mit mehr SSD oder Reset — Schluss mit Plattenangst
CI-Knoten mit wiederholten Alarmen? Diagnoseskript aus dem Artikel — oft 30+ GB in 10 Minuten. Langfristig: dedizierte Cloud Mac Mini — skalierbar, ohne Festplatten-Pflege.