2026-04-24 租用雲端 Mac 上 xcodebuild CI 的 Swift 6 嚴格並發、Sendable 與 @MainActor
已在 香港 / 日本 / 韓國 / 新加坡 / 美國 採用 Apple Silicon 主機建置 iOS 與 macOS 的負責人,將面臨第二波升級:Swift 6 語言模型配合嚴格並發檢查,會把「我機器上能過」放大成暖筆電與租用 SSH Mac 上冷啟動、高並行 xcodebuild 作業之間的可量化差距。這篇 2026-04-24 手冊不是語言課——它映射建置設定(SWIFT_STRICT_CONCURRENCY、Swift 語言版本)、DerivedData 與 index store 隔離,以及針對在 並行 xcodebuild 與 自架 Runner 拓撲中爆炸的 Sendable / nonisolated / MainActor 雜訊的分階段遷移。請交叉閱讀 暫存 DerivedData 與 xcresult 隔離 以及 遠端 archive,避免新加坡的同一建置節點劇烈爭搶共享 ModuleCache,而美國分片誤以為目錄是私有的。
為何 Swift 6 嚴格並發在無頭 CI 裡表現不同
嚴格檢查會暴露模擬器難以觸發的資料競爭:UI 主執行緒會掩蓋被擷取 var 的生命週期。在 CI 中,並行的 swift-frontend 作業與全模組最佳化會重排診斷;同一套件在 64 GB 開發筆電上過夜能通過,卻可能在關閉增量、開啟 -warnings-as-errors 的 lane 上失敗。請像對待已退役的 bitcode 封存要求或 僅 CLT 與完整 Xcode 一致性那樣對待嚴格並發:獨立流水線階段、固定的 DEVELOPER_DIR,以及書面政策——在重構落地前,哪些目標可暫時留在 SWIFT_STRICT_CONCURRENCY=targeted。
也請預留索引重建的時間:在全新的 DerivedData 上第一次以 Swift 6 全量建置,若產品含 App Intents 或較重的 WidgetKit 擴充功能,ExtractAppIntentsMetadata 及相關步驟可能多耗數分鐘。牆鐘的跳變不是並發迴歸,而是和首次 SwiftPM 註冊表快取/解析 暖機一樣的冷快取現象,應一併向產品負責人預先說明。在儀表板留下前後的 p95,讓「Swift 6 週」不會被讀成雲端建置產能下降。
建置設定:語言版本與嚴格並發模式
在專案或 xcconfig 層對齊三個旋鈕:(1) SWIFT_VERSION 對齊你要交付的 Swift 6 工具鏈;(2) SWIFT_STRICT_CONCURRENCY 在合併阻斷 lane 用 complete,但對遺留三方靜態函式庫在能加標註前可用 targeted;(3) Swift 6 下依目標的預設隔離——UIKit 與 SwiftUI 模組常需要顯式 @MainActor 覆蓋。在 CI YAML 中僅在 scheme 尚無法承載時傳入覆寫,例如建議 lane 使用 OTHER_SWIFT_FLAGS=-warn-concurrency,而不要悄悄混進必須位元級可複現的 release archive。區分套件外掛與應用程式目標:外掛建置日誌可能隱藏錯誤,直到你為真正交付的二進位啟用 -strict-concurrency=complete。
xcodebuild 呼叫:Archive、Build 與 Analyze
在主機上固定單一 DEVELOPER_DIR=/Applications/Xcode.app,並以顯式 -configuration 與 -destination 避免 scheme 漂移。若要在 UI 測試前做嚴格僅編譯門檻,優先 xcodebuild -scheme App build -destination 'platform=iOS Simulator,name=iPhone 16' 並配合 CODE_SIGNING_ALLOWED=NO——隨後第二個作業在開啟簽名的情況下跑 模擬器 測試,與 XCTest + xcresult 指南中的拆分一致。最小嚴格門檻範例:
DEVELOPER_DIR=/Applications/Xcode.app xcodebuild build -workspace App.xcworkspace -scheme App -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 16' -derivedDataPath "$CI_DERIVED/Swift6Gate" OTHER_SWIFT_FLAGS='$(inherited) -warn-concurrency'
Sendable 連續兩次相同失敗,應視為真實程式缺陷,而非 Xcode 幽靈。
並行 lane、DerivedData 與 Index Store
扇出時請參見 M4 扇出:為每個子作業提供獨立 -derivedDataPath,並可在嚴格階段關閉增量以使 HK / JP / KR / SG / US 建置機上的診斷確定化。在類 NFS 掛載上共享 DerivedData 最快招來幽靈錯誤——例如「cannot assign through subscript: base is not a concurrent value」在重試後消失——這正是 隔離 一文對 xcresult 已警告的抖動模式。若因成本必須共享卷,至少透過 $(CI_JOB_ID) 為 Index/DataStore 加 per-job 前綴。
遷移矩陣:告警、錯誤與負責人
| 訊號 | 動作 | 負責人 |
|---|---|---|
Sendable 閉包擷取 |
重構為值型別、標註 Sendable,或遷入 actor | 功能小隊 |
| MainActor 隔離漂移 | 透過小型 façade 型別串起 UI 更新 | 用戶端負責人 |
| 無 Sendable 的三方二進位 | 在模組邊界包裝、追蹤廠商議題或 fork | 建置平台 |
當它不是並發錯誤時
部分失敗會偽裝成 Swift 6 問題:描述檔與鑰匙圈 在 CodeSign 階段的顛簸會與 swift-frontend 日誌交錯;archive 後的 dSYM 缺失影響符號化而非編譯器。請把 dSYM 衛生 與嚴格 lane 放進同一週回顧,否則 on-call 會追錯 diff。
簽名、權利與同一建置中的「嚴格」
在同一發布列車中同時加碼編譯器檢查與描述檔輪換風險很高。建議順序:(1) 在一次性金絲雀上證明簽名全綠;(2) 啟用嚴格階段;(3) 再將特性開關合併為預設 scheme 強制執行 SWIFT_STRICT_CONCURRENCY=complete。同一池 SSH Mac mini M4 可承載兩者,但作業不應在同一 workspace 目錄交錯並行——只有遵循 archive 與 模擬器 文件時,並行才是優勢。
相關 MacXCode 指南
若並發失敗實為相依性圖不一致,請從 SwiftPM 與 registry 快取 以及 Ruby + CocoaPods 確定性 入手。若團隊混用 Xcode Cloud 與專用主機,請兩邊對齊語言旗標,否則專用嚴格 lane 會否決 Xcode Cloud 從未見過的合併。
常見問題:共享建置機上的 Swift 6
| 問題 | 實務回答 |
|---|---|
| 應先對 SPM 套件啟用嗎? | 通常是——先映射邊界,再處理 app 目標;若拆分僅測試程式碼可使用 package traits。 |
| 需要 VNC 嗎? | 一般不需要——VNC 是視覺化除錯的應急手段,不是嚴格建置日誌所必需。 |
| 索引碟容量? | 關閉大型 SwiftUI 預覽但保留龐大 Index 樹時,選擇 1–2 TB 主機;嚴格 lane 使產物 churn 增至 3× 時參見 定價。 |
為何 Mac mini M4 裸金屬仍適合並發繁重的編譯
嚴格檢查既吃 CPU 又吃 I/O:編譯器重寫圖、索引膨脹,index store 與你在 MacXCode 租用機上為 archive 預算的 NVMe 同樣受益。無嘈雜鄰居的通訊端堆疊也能在 lane 與遠端快取或registry 對話、解析套件時壓低尾延遲。當嚴格流水線穩定後,在把 lane 收回共享 DerivedData 之前,先在同一區域橫向擴容第二台 Mac mini M4——你的 2026 主幹值得確定性訊號,而不是抽獎式建置。