凌晨兩點,CI 流水線發出告警:建置節點磁碟剩餘不足 2 GB,xcodebuild 直接退出。你打開終端機,df -h 一看,256 GB 的 SSD 被塞滿了——~/Library/Developer 目錄獨占 180 GB,其中一個叫 DerivedData 的目錄就占了 90 GB 以上。
這不是偶發事故。在一台跑了 8 個月、未做任何清理的 M4 Mac Mini 上,DerivedData 平均能累積 40–100 GB;加上模擬器映像、裝置符號表,~/Library/Developer 合計超過 150 GB 並不罕見。本文從根因分析→快速定位→分類處理→CI 防阻塞→自動化維護五個環節,給你一套可以直接照抄的操作手冊。
讀完這篇文章,你會得到:DerivedData 各子目錄的實際用途與安全性評估、一條命令定位最大磁碟占用源、CI/本機雙場景的清理腳本,以及一套讓磁碟告警永不再觸發的自動化方案。
Quick Answer:三個最常見問題
| 問題 | 直接答案 | 風險 |
|---|---|---|
| DerivedData 能直接刪嗎? | rm -rf ~/Library/Developer/Xcode/DerivedData/* 完全安全 |
下次建置變全量,耗時增加約 2–5 分鐘 |
| Archives 能刪嗎? | 已無線上使用者的版本可刪;現役版本保留 dSYM | 刪後無法符號化舊版崩潰日誌 |
| CI 節點磁碟快滿了怎麼快速救急? | DerivedData → 模擬器 unavailable → 舊版 DeviceSupport,依序清理 | 下次建置需重建索引,約慢 3 分鐘 |
DerivedData 究竟存了什麼
很多開發者只知道「清 DerivedData 能解玄學問題」,但不清楚裡面到底放了什麼。這會導致一個後果:要么刪了不該刪的,要么留著該刪的、磁碟一次次撐爆。下表列出 ~/Library/Developer 下最重要的幾個路徑:
| 路徑 | 內容 | 典型大小 | 安全刪除? |
|---|---|---|---|
Xcode/DerivedData |
編譯產物、索引、建置日誌、Swift 模組快取 | 5–60 GB | ✅ 完全安全,下次建置自動重建 |
Xcode/Archives |
發布建置(.xcarchive)含 dSYM 符號表 | 2–30 GB | ⚠️ 舊版可刪,現役版本保留 dSYM |
Xcode/iOS DeviceSupport |
連接實機時下載的除錯符號 | 1–3 GB / 每 iOS 版本 | ✅ 舊版可刪,重連裝置自動重下 |
CoreSimulator/Profiles/Runtimes |
模擬器執行時期映像 | 5–10 GB / 每 iOS 版本 | ⚠️ 不用的版本可刪,需重下 |
CoreSimulator/Devices |
已建立的模擬器實例資料 | 1–15 GB | ✅ unavailable 狀態可安全刪 |
Caches/org.swift.swiftpm |
SPM 套件快取 | 1–8 GB | ✅ 可刪,下次 resolve 自動重建 |
Caches/com.apple.dt.Xcode |
Xcode 內部快取(預覽、IB) | 0.5–3 GB | ✅ 可刪 |
DerivedData 子目錄結構
每個專案在 DerivedData 下有一個獨立子目錄,格式為 {專案名}-{隨機雜湊}/,內部結構如下:
MyApp-abcdefghijklmn/
├── Build/
│ ├── Products/ # 編譯產物(.app, .framework, .o 檔案)
│ └── Intermediates.noindex/ # 中間檔案(.o, .d 依賴圖)
├── Index.noindex/ # SourceKit 索引(程式碼跳轉、自動補全)
├── Logs/ # 建置日誌(xcodebuild -resultBundlePath 也在這裡)
└── info.plist # 專案路徑記錄
其中 Build/Intermediates.noindex 通常最大——一個含 200 個 Swift 檔案的中型專案,這個目錄輕鬆超過 2 GB;加上 Index.noindex,單個專案的 DerivedData 目錄達到 5–10 GB 並不罕見。
五步快速定位磁碟阻塞元凶
不要一上來就無差別清理。花 3 分鐘定位,比亂刪省時省力。下面這套流程在一台 256 GB M4 Mac Mini 上實測,可在 2 分鐘內找到占用 Top 5 的目錄。
Step 1:先看磁碟整體使用情況
# 查看全碟使用率
df -h /
# 快速定位前 10 大目錄(整個根碟)
sudo du -sh /* 2>/dev/null | sort -rh | head -10
Step 2:鎖定 Developer 目錄的具體分布
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
實測輸出範例(跑了 8 個月的 CI 節點):
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
Step 3:找出 DerivedData 裡最肥的子專案
du -sh ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null | sort -rh | head -10
Step 4:查看模擬器占用與狀態
# 列出所有模擬器及狀態
xcrun simctl list devices
# 只看「已不可用」(系統升級後遺留)
xcrun simctl list devices | grep -i "unavailable"
Step 5:確認 DeviceSupport 的版本分布
ls -lh ~/Library/Developer/Xcode/iOS\ DeviceSupport/
輸出如下時,說明累積了多年的裝置符號檔案:
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)
iOS 15、16 的符號表在 2026 年幾乎沒有保留價值(App Store 最低版本要求已普遍達到 iOS 17),可以放心刪掉。
各目錄處理策略:能刪、慎刪、不能刪
可以直接刪(零風險)
# 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/*
選擇性刪除(刪舊留新)
# 刪除 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*
需要謹慎的:Archives
- 確認該版本已從 App Store 下架或無活躍使用者
- 若仍有使用者:單獨匯出 dSYM(Organizer → 右鍵 Archive → Show in Finder → 取出 .dSYM)
- 將 dSYM 上傳至 Firebase Crashlytics / Sentry 等崩潰分析平台
- 確認平台已收到 dSYM 後,再刪除對應 Archive
一鍵救急命令(磁碟告急時)
以下命令按安全順序執行,通常可快速釋放 20–60 GB:
#!/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 流水線防阻塞方案
本機開發機手動清理還好應付,但 CI Runner 長期無人值守,更需要系統性防護。這裡以常見的自架 macOS Runner(GitHub Actions / GitLab CI)為例,給出三套方案。
方案 A:固定 DerivedData 路徑 + 增量快取
把 DerivedData 指向可控路徑,配合 CI 快取機制實現跨 Job 複用:
# .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 和 project.pbxproj 的雜湊,否則 Swift Package 升級後會命中舊快取導致建置失敗。
方案 B:建置前後自動清理老舊快取
# 在建置步驟之前:清理超過 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 "目錄為空"
方案 C:GitLab CI 磁碟容量門控
在 .gitlab-ci.yml 裡加一個前置檢查 Job,磁碟不足時直接 fail-fast,避免建置到一半因磁碟滿而崩潰,產生無效產物:
# .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
三套方案對比
| 方案 | 適用場景 | 實作成本 | 快取複用率 |
|---|---|---|---|
| A:固定路徑 + 增量快取 | 中大型專案,PR 頻繁,建置耗時敏感 | 中(需設定 cache action) | 高(跨 Job 複用) |
| B:定期清理老舊子目錄 | 多專案共用節點,磁碟中等充裕 | 低(一行 find 命令) | 中(當日複用) |
| C:容量門控 fail-fast | 任何場景,作為兜底保障 | 極低(5 行腳本) | 不影響複用 |
推薦三套方案疊加使用:A 提升建置速度,B 控制磁碟增長,C 作為最後防線。
自動化維護:定時腳本 + 容量預警
手動清理終究不可靠。把清理邏輯寫成腳本,掛到 macOS launchd 定時執行,磁碟問題從被動應急變為主動防禦。
完整維護腳本
#!/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 "=== 維護完成 ==="
註冊為 launchd 定時任務
將腳本註冊為每週日凌晨 3 點自動執行:
# 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 "定時任務已註冊"
雲 Mac 如何從根本上規避磁碟困境
以上所有方案都是在「有限磁碟」的前提下做緩解。如果你的團隊正在評估是否升級 CI 基礎設施,有一個選項值得認真考慮:用雲端獨享 Mac Mini 替代本機節點。
磁碟管理問題在雲端架構下有幾個不同的解決路徑:
- 加購擴展 SSD:Macstripe 的 M4 Mac Mini 支援擴展儲存,直接買更大的碟比手動清理省力得多
- 週期性重置節點:按月計費的節點在週期結束後可以重新開通一台乾淨的機器,DerivedData 從零開始,無需任何清理腳本
- 按需擴容:版本發布衝刺期增加節點數量分擔建置負載,低峰期縮減,無需為高峰容量長期付費
- SSH 直連除錯:當節點出現玄學建置問題時,
ssh runner@your-node直接登入排查,比遠端管理實體機方便
一個實際案例:某行動端團隊將 3 台本機 Mac Mini 替換為 Macstripe 雲節點後,每月節省的維運時間(清理磁碟、更新 Xcode、排查 Runner 故障)約 8–12 小時。按工程師時薪計算,光維運成本就覆蓋了租賃費用的大部分。
對於已有 GitHub Actions 工作流的團隊,也可以參考這篇關於 實測 Windows 能獨立開發 iOS!別再買 Mac? 的文章,了解在不同平台上建置 iOS 應用時的完整對比。
FAQ
Q:直接刪除 DerivedData 會不會影響原始碼或發版記錄?
不會。DerivedData 只存放編譯產物、SourceKit 索引和建置日誌,與你的原始碼、Git 記錄、Provisioning Profile、App Store Connect 提交完全獨立。刪除後 Xcode 會在下次建置時完整重建,唯一的代價是全量建置而非增量建置,中型專案約多花 2–5 分鐘。
Q:Archives 目錄可以隨便刪嗎?
不能隨便刪。Archives 裡的 .xcarchive 包含 dSYM 符號表,用於符號化線上崩潰日誌。刪除前請確認:① 該版本已從 App Store 下架且無活躍使用者;② 或已將 dSYM 上傳至 Firebase Crashlytics / Sentry 等平台。建議保留最近 3 個發布版本的 Archive,更早的可在匯出 dSYM 後刪除。
Q:CI 環境每次建置都全量重建,有辦法複用快取嗎?
可以。把 -derivedDataPath /tmp/derived-data 傳給 xcodebuild,再結合 GitHub Actions actions/cache 或 GitLab CI 的 cache: 設定做增量同步。Cache key 需要包含 Package.resolved 和 project.pbxproj 的雜湊,避免不同依賴版本互相污染。實測對 Swift Package 較多的專案,增量建置可節省 40–60% 時間。
Q:磁碟空間不足導致 xcodebuild 直接失敗,如何快速恢復?
按優先順序依序執行:① rm -rf ~/Library/Developer/Xcode/DerivedData/*(通常釋放 10–60 GB);② xcrun simctl delete unavailable(釋放 5–20 GB);③ 刪除兩個版本以上的 iOS DeviceSupport 條目(釋放 5–15 GB)。大多數情況下第①步就能解決問題。
Q:Xcode 的「Product > Clean Build Folder」和命令列刪除 DerivedData 有什麼區別?
Clean Build Folder 只清理目前開啟專案的建置產物,對其他專案的快取、模擬器資料、DeviceSupport 沒有影響。命令列 rm -rf ~/Library/Developer/Xcode/DerivedData/* 會清掉所有專案的快取。CI 節點維護時建議用命令列,開發機日常除錯用 Clean Build Folder 更精準。
Q:在 CI Runner 上,DerivedData 增長特別快怎麼處理?
原因通常是:多個 Scheme / 多個分支並行建置,每次都產生新的 DerivedData 子目錄。建議:① 用 -derivedDataPath 固定到單一路徑;② 在建置腳本裡加 find ... -atime +7 -exec rm -rf {} + 清理 7 天未存取的子目錄;③ 評估是否需要升級節點磁碟容量或使用 Macstripe 雲 Mac 的擴展 SSD 選項。
總結
DerivedData 磁碟阻塞是 iOS 開發者幾乎必然會遇到的問題,但它完全可以透過系統性手段解決,而不是每次「玄學」發作時手忙腳亂地清理。
- 理解根因:DerivedData 是純快取,可安全刪除;Archives 含 dSYM,需謹慎處理
- 快速診斷:
du -sh ~/Library/Developer/Xcode/* | sort -rh一條命令定位元凶 - 分類處理:DerivedData 無腦刪,DeviceSupport 刪舊留新,Archives 先匯出 dSYM 再刪
- CI 防護:固定
-derivedDataPath+ 增量快取 + 容量門控三層保障 - 自動化:用 launchd 掛載維護腳本,每週自動清理,從被動應急轉為主動防禦
- 從根本解決:評估雲 Mac 方案,透過擴展 SSD 或週期性重置節點徹底告別磁碟焦慮
如果你的團隊正面臨 CI 節點磁碟頻繁告警、建置中途失敗的困擾,不妨先跑一次文中的診斷腳本,通常 10 分鐘內就能釋放 30 GB 以上。長期來看,將 CI 基礎設施遷移到雲端獨享 Mac Mini 是更可持續的解法——既能按需擴容,又無需擔心磁碟維護。