macOS 磁盘清理实战:安全释放 Xcode、Docker 与 APFS 占用

type
status
date
slug
summary
tags
category
icon
password
wechat_gate
notion image
一次真实的 macOS 磁盘清理,把可用空间从 48.0GB 提升到 92.4GB,随后稳定在 97.6GB。真正有效的不是某款清理软件,而是一套可复核的方法:先定位 Xcode 模拟器和 Docker 的实际占用,再区分 APFS 表观体积与真实可回收空间,最后只删除能明确说明用途和后果的对象。
这次由 AI agent 协助做目录审计、命令解释和候选项排序,但所有破坏性操作都由我根据清单逐项确认。过程中最有价值的收获不是“释放了 44.4GB”,而是纠正了三个容易导致误判的认识:磁盘工具的分类不等于完整扫描,du 的结果不等于删除收益,docker system prune --volumes 也不等于清空所有卷。

macOS 磁盘清理,先建立三个不同的数字

开始清理前,先把三个容易混在一起的概念分开:
数字
它回答的问题
常用工具
目录表观体积
目录树中的文件看起来有多大
du -sh
文件系统可用空间
当前还能实际写入多少数据
diskutil info /df -h /
工具分类占用
工具识别并归类了多少数据
系统设置、磁盘分析工具、Docker Desktop
三者可以同时正确,却给出明显不同的结果。原因包括 APFS clone 共享数据块、快照、稀疏文件、延迟回收,以及扫描工具没有覆盖系统级目录。
我的起点是一个终端磁盘分析工具给出的分类结果:User Library 174GB、Home 88GB、Applications 42.8GB、Xcode Simulators 24.8GB、Docker Data 5.4GB。报表看起来足够详细,但继续往下检查后,又发现两块没有被单独呈现的大目录:
  • /Library/Developer/CoreSimulator/Volumes/:66GB;
  • ~/Library/Developer/XCTestDevices/:57GB。
前者位于系统级 /Library,后者被折叠进 User Library 汇总。问题不一定是工具算错,而是它的扫描边界和分类口径与我的问题不同。我想知道的是“哪些开发资产可以安全移除”,不是“哪些顶层目录最大”。
这组命令的作用只是审计,不删除任何文件。把“扫描”和“删除”拆开,是整套流程最重要的安全边界。
notion image

为什么删除 59GB,空闲空间只增加了几 GB

第一批候选项包括 XCTestDevicesDerivedData、Homebrew 缓存和废纸篓。从目录统计看,处理后 ~/Library/Developer 从约 89GB 降到 30GB,表观体积减少约 59GB;但同一阶段观察到的文件系统可用空间只从 48.0GB 增加到约 51.3GB。
我一开始怀疑是删除不彻底,又检查了本地 Time Machine 快照、swap 和近期新增的大文件。没有发现能解释差额的增长项。结合 Apple 对 APFS clone 的说明,更合理的解释是:这些目录中的一部分文件与其他对象共享底层数据块,du 统计了文件的逻辑大小,但删除一个引用并不会释放仍被其他文件引用的数据块。
Apple 对 APFS 的定义很直接:同一卷内创建的 clone 起初几乎不增加磁盘占用,后续修改的数据才写入新位置,未修改的数据块继续共享。因此,对 clone 密集的目录,下面这个等式不成立:
更可靠的验证方式是同时记录目录变化和卷的可用空间:
如果目录明显变小,而卷的空闲空间只小幅变化,不要立刻重复删除或扩大清理范围。先检查 clone、快照、仍被进程打开的文件,以及系统的延迟回收。
notion image

code_sign_clone 的 40GB,只能证明累积,不能证明原因

继续审计用户临时目录时,我发现一个与 Chrome 相关的 code_sign_clone 目录,du 显示约 40GB;同一区域还有 Edge 和其他应用留下的同类目录。文件时间戳横跨近三周,说明它确实长期累积。
但这里必须区分事实与推测:目录名称、文件结构和时间戳能证明存在应用克隆及其累积,不能单独证明一定是 Gatekeeper、App Translocation 或 Chrome 更新流程中的某个 Bug。原始材料没有系统日志或可复现步骤,不能把机制猜测写成确定结论。
安全处理方式仍然明确:先定位当前用户对应的临时目录,确认相关应用完全退出,再移动到废纸篓观察;不要直接在 /private/var/folders 下做模糊匹配删除。
这次处理 40GB 表观体积后,实际空闲空间只增加约 2.4GB,再次符合共享数据块的特征。这里能复用的结论是:看到带 clonesnapshotshadow 的目录时,先假设它可能共享数据,不要按 du 结果承诺回收量。

Xcode 模拟器真正的大头:runtime 与 device 不是一回事

Xcode 模拟器相关数据至少要分成两类:
  • Simulator runtime:某个 iOS、watchOS 等系统版本的运行环境,一份 runtime 可以被多台模拟设备复用;
  • Simulator device:具体设备实例,包含机型、应用数据和运行状态。
Apple 官方文档也明确区分了这两个对象:runtime 是系统包,多台 Simulator 可以复用;不用的 runtime 可以在 Xcode 的 Components 设置中移除,设备实例则在 Devices and Simulators 中管理。
这台 Mac 同时保留了 iOS 18.6、26.0、26.2 和 26.4 四套 runtime。/Library/Developer/CoreSimulator/Volumes/ 的目录统计合计约 66GB,其中两个已不再需要的版本占据了最大的可回收部分。
45 台设备中,只有 9 台目录达到 1.6GB 至 4.3GB,另外 36 台每台约 17MB。这个结果不能自动推出“36 台都该删”,但能帮助我把决策从机型数量转向实际测试需求:保留最新版 26.4 和最低支持版本 18.6,移除不再用于回归测试的 26.0 与 26.2。
优先使用 Xcode 提供的管理入口:
  1. 打开 Xcode > Settings > Components
  1. 在已安装平台中确认目标 runtime 的版本和可回收空间;
  1. 删除不再需要的 runtime;
  1. Window > Devices and Simulators 中复核设备实例。
命令行适合批量审计,但不要把手工删除 /Library/Developer/CoreSimulator 目录当成首选方案。Xcode 知道组件之间的关系,也能在未来需要时重新下载 runtime。
这一阶段可用空间从 61.7GB 提升到 92.4GB,净增加 30.7GB,是整次清理中最确定的一笔收益。原因不是所有 Simulator 数据都能一比一回收,而是被移除的整套 runtime 不再被保留设备引用。

Docker 的坑不在 prune,而在把命令名理解错了

Docker Desktop 启动后,我先执行 docker system df:19 个镜像只有 1 个活跃,约 2.072GB 可回收;1 个已退出容器约 2.959MB;还有一个 1.317GB 的 minikube 命名卷,被一个 12 个月前退出的容器引用。
执行 docker system prune -a --volumes 后,镜像和停止容器被清理,minikube 卷仍然存在。最初我把它当成 Docker 没清干净,核对官方文档后才发现,--volumes 的定义就是额外清理未使用的匿名卷,不承诺删除命名卷。
这不是 Docker Bug,而是我把“volumes”理解成了“所有卷”。这个失败案例比回收的 1.3GB 更重要:命令行参数看起来熟悉,不代表语义和直觉一致。
清理前应该先建立卷与容器的关联:
只有确认卷中的集群状态、数据库或其他持久化数据不再需要时,才执行精确删除:
不要为了省一步,把它改成没有清单的批量删除。命名卷存在的意义就是持久化数据,它比镜像缓存更值得保守处理。

一份不能机械相加的清理账本

这次记录同时包含“命令完成后的即时读数”和“系统后台回收后的稳定读数”,采样时间不同,因此各阶段增量不能机械相加。可以确认的总结果是:一次会话从 48.0GB 到 92.4GB,净增加 44.4GB;之后稳定到 97.6GB。
对象
表观体积或审计值
直接观察到的结果
结论
XCTestDevices、DerivedData 等
目录减少约 59GB
空闲空间只增加几 GB
clone 共享使 du 高估删除收益
code_sign_clone
约 40GB
空闲空间增加约 2.4GB
能证明累积,不能仅凭目录证明根因
Docker 镜像、容器与命名卷
约 7GB
Docker 数据明显收缩
匿名卷与命名卷要分别审计
两套旧 Simulator runtime 与闲置设备
约 35GB
空闲空间增加 30.7GB
删除完整 runtime 的收益最确定
这份账本还有一个重要的“未删除项”:25 个 Xcode Archives 合计约 707MB,其中部分像重复打包产物,但无法确认哪些归档曾用于 App Store 提交。为了不到 1GB 的空间,承担丢失 dSYM、影响历史版本崩溃符号化的风险,不值得。因此我只列出候选项,没有执行删除。

可复用的安全清理流程

以后再做 macOS 磁盘清理,我会固定按下面的顺序执行。

1. 建立基线

记录卷可用空间、开发目录一级分布、Xcode runtime、Simulator device、Docker 镜像和卷。没有基线,就无法判断某一步到底回收了多少。

2. 给候选项分级

  • 可重建缓存:DerivedData、包管理器缓存,通常风险较低;
  • 可重新下载组件:不用的 Simulator runtime,需要先确认测试矩阵;
  • 持久化数据:Docker 命名卷、模拟器应用数据,需要逐项确认;
  • 发布资产:Xcode Archives 和 dSYM,默认保留,除非已有可靠备份和提交记录。

3. 每次只处理一类对象

不要同时删除 Xcode、Docker 和系统临时目录。每完成一类,就记录 dudiskutil,这样差额出现时才能定位原因。

4. 用产品提供的管理入口删除

Simulator runtime 优先用 Xcode Components,Docker 对象优先用 Docker CLI,Homebrew 缓存用 brew cleanup。直接删除内部目录只应作为有明确依据的最后手段。

5. 删除后复核残留和能力

确认 Xcode 仍能打开项目、目标 runtime 仍覆盖最低支持版本与当前版本、Docker 必要服务可以重建、历史归档仍可用于符号化。空间增加不是唯一验收标准。

总结

这次清理真正解决的不是一个 44.4GB 的空间问题,而是建立了一个更可靠的判断模型:工具分类只代表扫描口径,du 只代表目录逻辑体积,APFS clone 会让删除收益远小于表观值;Xcode runtime 与设备数据必须分开处理;Docker 的匿名卷和命名卷也必须分开审计。
AI agent 很适合做路径枚举、数据汇总和命令解释,但不应该替人决定哪些历史归档、测试环境或持久化卷可以消失。最稳妥的协作方式是让 AI 生成候选清单和验证步骤,把不可逆决策留给掌握业务背景的人。

参考资料

 
2026.08.20 22:00 沪 · 赵巷