Mac-Entwickler analysiert im Terminal die Speicherbelegung von Xcode DerivedData

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
Kernaussage: Alles unter DerivedData ist wiederaufbaubarer Cache — unabhängig von Quellcode, Provisioning Profile und App-Store-Einreichungen. Einziger Preis: nächster Build wird vollständig (mittleres Projekt ca. 2–5 Min. mehr).

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
Gemessen: Auf einer Maschine mit 15 historischen Projekten: Top 3 mit 18 GB, 12 GB und 9 GB — zwei Projekte seit über einem Jahr ungeöffnet, 30 GB verschwendet.

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 /
💡 Grundlegende Lösung: Ist Ihr CI-Knoten eine lokale Maschine, bleibt Festplattenmanagement Daueraufgabe. Macstripe bietet dedizierte M4 Mac Mini in der Cloud — tageweise, wöchentlich oder monatlich, Knoten nach Nutzung zurücksetzen — DerivedData-Stau entfällt von vornherein. Preispläne ansehen →

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 }}-
Hinweis: Der Cache-Key muss 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 "定时任务已注册"
Gemessen: Ein Team mit vier selbst gehosteten Mac-Mini-CI-Knoten: drei Monate ohne Festplattenalarm. Wöchentlich 3–5 GB frei, stets über 60 GB verfügbar.

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.

Macstripe Cloud-Mac: Dedizierte physische M4 Mac Mini, fünf Regionen (Singapur / Japan / Korea / Hongkong / US-West), SSH + VNC, ~5 Min. Bereitstellung, tageweise ohne Langzeitvertrag. Pakete und Einstiegspreise →

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 -rh findet 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.

Weiterlesen