云端 Mac 文件同步实战:用 Mutagen 打通本地与远程的 iOS 工程

远程 Mac ·约 8 分钟阅读

云端 Mac 文件同步实战:用 Mutagen 打通本地与远程的 iOS 工程

项目仓库快 40GB,Pods、node_modules、DerivedData 加起来比源码本身还大。你在本地打开 Xcode,项目挂载在远端云端 Mac 的一个 SMB 共享盘上,结果索引转了一分多钟,切个分支卡到怀疑网络掉线——这是很多把开发环境搬到云端 Mac 之后第一个撞上的墙。挂载共享盘看起来省事,实际用起来才发现,大型 iOS 工程的文件访问模式跟网络文件系统天生不合拍。这篇文章记录我们在 HireVPS 节点上用 Mutagen 做本地与远程双向同步,把这套体验拉回接近本地的过程。

挂载共享盘为什么不适合大型 iOS 工程

NFS、SMB 这类网络文件系统的设计前提是「偶尔访问的大文件」,而 Xcode 索引、SourceKit、Pods 的 CocoaPods 缓存扫描,做的恰恰是相反的事:短时间内对几万个小文件做 stat、open、close。每一次操作都要走一趟网络往返,哪怕延迟只有 20ms,乘以几万次也是分钟级的卡顿。更麻烦的是断网处理——挂载点在连接抖动时会直接假死,终端和编辑器一起卡死,只能强制卸载重新挂载。

三种常见方案对比一下会更直观:

方案 实时性 大量小文件表现 断线恢复 适用场景
SMB/NFS 挂载 强(所见即最新) 差,逐文件网络往返 差,容易假死 偶尔读写大文件
rsync 定时任务 弱,有同步窗口 一般,批量传输效率高 好,任务制重跑即可 定期备份、单向推送
Mutagen 双向同步 接近实时 好,本地缓存+增量传输 好,断线自动重连续传 持续开发的双向协作

我们踩过的一个教训是:一开始图省事直接挂载,结果 Xcode 的「Building workspace」这一步平均要多等 15~25 秒,团队每天光是等待索引就浪费不少时间,换成 Mutagen 之后这个等待基本消失。

用 Mutagen 建立双向同步会话

安装与首次连接

Mutagen 本身是单一二进制,本地和云端 Mac 都不需要额外常驻服务,连接建立时会自动把 agent 分发到远端。本地先安装:

brew install mutagen-io/mutagen/mutagen
mutagen version

假设你的云端 Mac 已经开好 SSH,凭据在开机邮件里。创建一个同步会话:

mutagen sync create \
  --name=ios-app \
  --ignore-vcs \
  --symlink-mode=posix-raw \
  /Users/me/Projects/ios-app \
  ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app

--ignore-vcs 会自动跳过 .git 内部对象(git 本身用自己的协议同步更合适,不需要 Mutagen 重复搬),--symlink-mode=posix-raw 让 CocoaPods 里常见的符号链接原样保留,不做转换。

忽略规则:哪些目录绝对不能同步

工程目录下一定要建一份 .mutagenignore,否则第一次同步就会把几个 GB 的缓存目录也传一遍:

DerivedData/
Pods/
.build/
node_modules/
*.xcuserstate
xcuserdata/
.DS_Store

这些目录的共同点是「可以在任意一端本地重新生成」,同步过去不但浪费流量,还可能带来下面这节说的坑。

Xcode 相关的几个坑

实测表现与冲突处理

日常使用下,一次保存触发的增量同步(改动几个 Swift 文件)基本在一两秒内完成,肉眼感觉跟本地保存差不多;第一次全量同步(几万个源文件)因为要扫描目录树,会明显慢一些,建议开工前先跑一次让它稳定,而不是边写代码边等首次同步。

冲突处理上,默认的双向模式是 two-way-safe——遇到冲突不会自动覆盖,而是停下来等你处理,这一点比强制覆盖的方案安全,但如果两边都在改同一个文件就会频繁暂停。团队协作场景更推荐:

mutagen sync create \
  --name=ios-app \
  --sync-mode=two-way-resolved \
  --default-file-mode-alpha=0644 \
  /Users/me/Projects/ios-app \
  ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app

two-way-resolved 会在冲突时默认让本地(alpha 端)胜出,配合「一个人同一时间只在一端改代码」的约定,基本不会丢改动。真遇到二进制资源(图片、字体)冲突,mutagen sync list 会列出冲突路径,手动比对时间戳和文件大小决定留哪份。

一份可以直接抄的配置

Mutagen 支持项目级配置文件,放在工程根目录的 mutagen.yml 里,团队成员克隆仓库后一行命令就能起同步,不用每个人手敲一遍参数:

sync:
  ios-app:
    alpha: "."
    beta: "ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app"
    mode: "two-way-resolved"
    ignore:
      vcs: true
      paths:
        - "DerivedData"
        - "Pods"
        - "node_modules"
        - "xcuserdata"
        - ".build"
    symlink:
      mode: "posix-raw"

常用的日常命令记这几个就够:

mutagen sync list
mutagen sync monitor ios-app
mutagen sync pause ios-app
mutagen sync resume ios-app
mutagen sync terminate ios-app

monitor 在排查「怎么改动没同步过去」时特别好用,能实时看到扫描、暂存、传输三个阶段各卡在哪一步。

上线前自查清单

把这几项过一遍,云端 Mac 的编辑体验基本就能贴近本地,团队协作时也不会再因为「谁的改动没同步过去」互相甩锅。

常见问题

为什么不直接用 SMB/NFS 把云端 Mac 目录挂载到本地?

网络挂载对大量小文件(如 Xcode 索引、Pods 目录)的每次 stat/open 都要走一次网络往返,大型 iOS 工程打开项目或索引常常卡到几十秒;Mutagen 走本地文件系统缓存加增量同步,交互延迟更低,断网也不会导致文件系统假死。

DerivedData 要不要同步?

不要。DerivedData 里的模块缓存、索引文件跟机器架构和本机路径绑定,同步过去大概率触发重新索引甚至编译报错,应该把它加进 .mutagenignore,让本地和云端各自独立生成。

双向同步出现冲突怎么处理?

先用 mutagen sync list 定位冲突文件,大多数场景选 two-way-resolved 模式并设置 alpha 端(本地)优先,极少数二进制资源冲突再手动 diff 决定保留哪一份,养成同步前 git status 清一次的习惯能避免大部分冲突。

HireVPS

今天就试试独享云 Mac mini

按天起租,2 分钟拿到 SSH/VNC 凭据,配置随时可升级。

立即租用 Mac mini