專案倉庫將近 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 相關的幾個坑
- DerivedData 絕對不能同步:裡面的模組快取(ModuleCache.noindex)和建置索引跟本機的絕對路徑、架構綁死,同步到另一台機器基本上等於強制觸發全量重新索引,嚴重時還會冒出莫名其妙的編譯錯誤。讓本機和雲端各自獨立產生即可,反正它本來就該是可丟棄的產物。
- Pods 與 node_modules 各自安裝:原始碼裡的
Podfile.lock、package-lock.json才是需要同步的東西,依賴目錄本身在兩端分別跑一次pod install、npm install,既省流量也避免二進位產物跨架構不相容。 .gitignore與.mutagenignore保持一致:兩份忽略清單如果打架,你會看到 git 顯示「無變更」,但 Mutagen 卻在背景傳一堆沒意義的檔案,排查起來很浪費時間,建議寫個小腳本每次變更後 diff 一下兩份清單。- 權限位差異:雲端 Mac 與本機帳戶的 UID 不同,遇到權限錯誤先看是不是同步把可執行位丟了,
--permissions-mode=portable能緩解大部分場景。
實測表現與衝突處理
日常使用下,一次儲存觸發的增量同步(改動幾個 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 在排查「怎麼改動沒同步過去」時特別好用,能即時看到掃描、暫存、傳輸三個階段各卡在哪一步。
上線前自查清單
.mutagenignore和.gitignore是否都覆蓋了 DerivedData、Pods、node_modules、xcuserdata 等可重建目錄?- 同步模式選的是
two-way-safe還是two-way-resolved?團隊協作場景優先後者,並約定好誰的變更優先。 - 首次全量同步是不是在沒有緊急任務的時段做的?數萬個檔案的初始掃描不適合邊寫程式碼邊等。
- 雲端 Mac 的具體機型與節點是否已在控制台確認目前可選配置?頻寬和磁碟規格會影響同步速度上限。
mutagen sync monitor是不是已經養成排查習慣,而不是等檔案丟了才想起來看日誌?
把這幾項過一遍,雲端 Mac 的編輯體驗基本上就能貼近本機,團隊協作時也不會再因為「誰的變更沒同步過去」互相甩鍋。
常見問題
為什麼不直接用 SMB/NFS 把雲端 Mac 目錄掛載到本地?
網路掛載對大量小檔案(如 Xcode 索引、Pods 目錄)的每次存取都要走一次網路往返,大型 iOS 專案開啟或索引常常要卡上幾十秒;Mutagen 走本地檔案系統快取加增量同步,互動延遲更低,斷線也不會讓檔案系統假死。
DerivedData 要不要同步?
不要。DerivedData 裡的模組快取、索引檔案跟機器架構與本機路徑綁定,同步過去很容易觸發重新索引甚至編譯錯誤,應該把它加進 .mutagenignore,讓本地與雲端各自獨立產生。
雙向同步出現衝突怎麼處理?
先用 mutagen sync list 定位衝突檔案,大多數情況選 two-way-resolved 模式並讓 alpha 端(本地)優先;少數二進位資源衝突再手動 diff 決定保留哪一份,同步前先跑一次 git status 清理工作區能避免大部分衝突。
HireVPS
今天就試試獨享雲端 Mac mini
按日起租,2 分鐘取得 SSH/VNC 憑證,配置隨時可升級。