Mac 開發者在終端機排查 Xcode DerivedData 磁碟占用問題

凌晨兩點,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 下的所有內容均為可重建快取,與原始碼、Provisioning Profile、App Store 提交記錄完全獨立。刪除後的唯一代價是下次建置變全量(中型專案約多 2–5 分鐘)。

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
實測數字:在一個有 15 個歷史專案的開發機上,top 3 的 DerivedData 子目錄分別為 18 GB、12 GB、9 GB——其中兩個專案已經超過一年沒打開過,白占了 30 GB。

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 節點是本機實體機,磁碟管理終究是個長期負擔。Macstripe 提供雲上獨享 M4 Mac Mini,可按天/週/月計費,用完即可重置節點——DerivedData 堆積問題從根本上消失。查看定價方案 →

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 }}-
注意:快取 key 必須包含 Package.resolvedproject.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 "定時任務已註冊"
實測效果:某團隊在 4 台自架 Mac Mini CI 節點上部署此腳本後,連續 3 個月未再出現磁碟告警。每週定期清理維持在 3–5 GB 的回收量,節點穩定保持 60 GB 以上可用空間。

雲 Mac 如何從根本上規避磁碟困境

以上所有方案都是在「有限磁碟」的前提下做緩解。如果你的團隊正在評估是否升級 CI 基礎設施,有一個選項值得認真考慮:用雲端獨享 Mac Mini 替代本機節點

磁碟管理問題在雲端架構下有幾個不同的解決路徑:

  • 加購擴展 SSD:Macstripe 的 M4 Mac Mini 支援擴展儲存,直接買更大的碟比手動清理省力得多
  • 週期性重置節點:按月計費的節點在週期結束後可以重新開通一台乾淨的機器,DerivedData 從零開始,無需任何清理腳本
  • 按需擴容:版本發布衝刺期增加節點數量分擔建置負載,低峰期縮減,無需為高峰容量長期付費
  • SSH 直連除錯:當節點出現玄學建置問題時,ssh runner@your-node 直接登入排查,比遠端管理實體機方便

一個實際案例:某行動端團隊將 3 台本機 Mac Mini 替換為 Macstripe 雲節點後,每月節省的維運時間(清理磁碟、更新 Xcode、排查 Runner 故障)約 8–12 小時。按工程師時薪計算,光維運成本就覆蓋了租賃費用的大部分。

Macstripe 雲 Mac:獨享實體 M4 Mac Mini,全球五節點(新加坡 / 日本 / 韓國 / 香港 / 美國西部),SSH + VNC 雙接入,約 5 分鐘開通,無長約可按天計費。查看套餐與起價 →

對於已有 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.resolvedproject.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 是更可持續的解法——既能按需擴容,又無需擔心磁碟維護。

相關閱讀