SKILL.md
readonlyread-only
name
swift-concurrency
description
診斷 Swift 並發問題,將基於回呼的程式碼重構為 async/await,並在處理任務、actor、@MainActor、Sendable、資料競爭、執行緒安全或與並發相關的編譯器及 linter 警告時,引導 Swift 6 遷移。
Swift 並發
快速路徑
在提出修復方案之前:
- 分析
Package.swift或.pbxproj以確定 Swift 語言模式、嚴格並發等級、預設隔離和即將推出的功能。務必執行此步驟,不僅限於遷移工作。 - 擷取確切的診斷資訊和違規符號。
- 確定隔離邊界:
@MainActor、自訂 actor、actor 實例隔離或nonisolated。 - 確認程式碼是否繫結 UI 或預期在主 actor 之外執行。在產生非結構化任務時,檢查同步前綴(第一個
await之前的所有內容):僅在該前綴確實需要主 actor 存取時才從@MainActor開始;否則使用Task { @concurrent in ... }並僅在暫停後透過MainActor.run跳回。一個微不足道的非主執行緒(例如print)後接主 actor 工作的情況,不構成使用@concurrent的理由。對於延遲重試、計時器和退避任務,將等待與 UI 變更分開。即使最終狀態更新屬於主 actor,sleep 通常也應在主 actor 之外執行。
會改變並發行為的專案設定:
| 設定 | SwiftPM (Package.swift) |
Xcode (.pbxproj) |
|---|---|---|
| 語言模式 | swiftLanguageVersions 或 -swift-version(// swift-tools-version: 不是可靠的代理) |
Swift 語言版本 |
| 嚴格並發 | .enableExperimentalFeature("StrictConcurrency=targeted") |
SWIFT_STRICT_CONCURRENCY |
| 預設隔離 | .defaultIsolation(MainActor.self) |
SWIFT_DEFAULT_ACTOR_ISOLATION |
| 即將推出的功能 | .enableUpcomingFeature("NonisolatedNonsendingByDefault") |
SWIFT_UPCOMING_FEATURE_* |
| 可親近並發 | 不適用(使用個別即將推出的功能) | SWIFT_APPROACHABLE_CONCURRENCY |
Xcode 26 注意:在 Xcode 26 中建立的新專案通常會預設啟用
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor和SWIFT_APPROACHABLE_CONCURRENCY = YES。將這些視為新建立專案的可能預設值,而非已確認的設定。
如果其中任何一項未知,請要求開發者確認後再提供遷移相關的建議。不要猜測,即使是針對新的 Xcode 26 專案。
護欄:
- 不要將
@MainActor作為萬能修復方案。請說明為何程式碼確實繫結 UI。 - 優先使用結構化並發而非非結構化任務。僅在有明確理由時使用
Task.detached。 - 如果建議使用
@preconcurrency、@unchecked Sendable或nonisolated(unsafe),需要記錄安全不變量並制定後續移除計畫。 - 以最小安全變更為最佳化目標。不要在遷移期間重構不相關的架構。
- 課程參考資料僅供深入學習使用。謹慎使用,僅在確實有助於回答開發者問題時才引用。
快速修復模式
當以下所有條件成立時,使用快速修復模式:
- 問題僅限於單一檔案或單一型別。
- 隔離邊界明確。
- 修復方案可以用 1-2 個保持行為的步驟解釋。
當以下任一條件成立時,跳過快速修復模式:
- 建置設定或預設隔離未知。
- 問題跨越模組邊界或變更公開 API 行為。
- 可能的修復方案依賴於不安全的逃脫機制。
常見診斷
| 診斷 | 首先檢查 | 最小安全修復 | 升級至 |
|---|---|---|---|
Main actor-isolated ... cannot be used from a nonisolated context |
這是否確實繫結 UI? | 將呼叫者隔離至 @MainActor,或僅在主 actor 所有權正確時使用 await MainActor.run { ... }。 |
references/actors.md、references/threading.md |
Actor-isolated type does not conform to protocol |
該要求是否必須在 actor 上執行? | 優先使用隔離的遵循(例如 extension Foo: @MainActor SomeProtocol);僅對真正非隔離的要求使用 nonisolated。 |
references/actors.md |
Sending value of non-Sendable type ... risks causing data races |
正在跨越哪個隔離邊界? | 將存取保留在單一 actor 內,或將傳遞的值轉換為不可變/值型別。 | references/sendable.md、references/threading.md |
SwiftLint async_without_await |
async 是否確實為協定、覆寫或 @concurrent 所要求? |
移除 async,或使用帶有理由的窄範圍抑制。永遠不要添加假的 await。 |
references/linting.md |
wait(...) is unavailable from asynchronous contexts |
這是舊版 XCTest 非同步等待嗎? | 替換為 await fulfillment(of:) 或 Swift Testing 的等效功能。 |
references/testing.md |
| Core Data 並發警告 | NSManagedObject 實例是否跨越上下文或 actor? |
傳遞 NSManagedObjectID 或映射為 Sendable 值型別。 |
references/core-data.md |
Thread.current unavailable from asynchronous contexts |
您是否透過執行緒而非隔離進行除錯? | 以隔離的角度思考,改用 Instruments/除錯器。 | references/threading.md |
| SwiftLint 並發相關警告 | 觸發了哪個特定的 lint 規則? | 使用 references/linting.md 了解規則意圖和偏好的修復方案;避免虛假的 await。 |
references/linting.md |
... cannot satisfy conformance requirement for a 'Sendable' type parameter (SendableMetatype) |
該遵循是否帶有全域 actor 隔離? | 從遵循中移除 actor 隔離,或避免在隔離邊界間傳遞 metatype。請參閱 references/actors.md 中的 SendableMetatype 章節。 |
references/actors.md |
當快速修復失敗時
- 如果尚未確認,請收集專案設定。
- 重新評估型別跨越了哪些隔離邊界。
- 導向匹配的參考檔案以取得更深入的修復方案。
- 如果修復方案可能改變行為,請記錄不變量並新增驗證步驟。
最小安全修復
偏好能在滿足資料競爭安全的同時保留行為的變更:
- UI 繫結狀態:將型別或成員隔離至
@MainActor。 - 共享可變狀態:將其移至
actor後方,或僅在狀態為 UI 所擁有時使用@MainActor。 - 背景工作:當工作必須跳離呼叫者隔離時,使用標記為
@concurrent的asyncAPI;當工作可以安全地繼承呼叫者隔離時,使用不帶@concurrent的nonisolated。在產生Task時,使其進入隔離與同步前綴匹配。如果第一個await之前沒有任何內容需要主 actor,則使用Task { @concurrent in ... }並透過await MainActor.run { ... }跳回以進行 UI 更新。如果前綴混合了微不足道的非主陳述式和主 actor 工作,則保留繼承的@MainActor起始點——將廉價的程式碼拆分到主 actor 之外不值得多一次跳轉。 - Sendability 問題:偏好不可變值和明確邊界,而非
@unchecked Sendable。
並發工具選擇
| 需求 | 工具 | 關鍵指引 |
|---|---|---|
| 單一非同步操作 | async/await |
順序非同步工作的預設選擇 |
| 固定並行操作 | async let |
編譯時已知數量;拋出時自動取消 |
| 動態並行操作 | withTaskGroup |
未知數量;結構化——在作用域退出時取消子任務 |
| 同步 → 非同步橋接 | Task { } |
繼承 actor 上下文;僅在有記錄理由時使用 Task.detached |
| 共享可變狀態 | actor |
優先於鎖/佇列;保持隔離區段簡短 |
| UI 繫結狀態 | @MainActor |
僅用於真正與 UI 相關的程式碼;說明隔離理由 |
常見情境
帶有 UI 更新的網路請求
Task { @concurrent in
let data = try await fetchData()
await MainActor.run { self.updateUI(with: data) }
}
並行處理陣列項目
await withTaskGroup(of: ProcessedItem.self) { group in
for item in items {
group.addTask { await process(item) }
}
for await result in group {
results.append(result)
}
}
任務進入隔離
將 Task 的進入隔離與其同步前綴(從 { 到第一個 await 之間的所有內容)匹配。
- 如果該前綴中的任何內容需要
@MainActor,則保留繼承的@MainActor起始點。 - 如果該前綴中沒有任何內容需要
@MainActor,則偏好使用Task { @concurrent in ... },並僅在需要 UI 所擁有的變更時跳回。
// ❌ 同步前綴為空;第一個工作跳離
Task {
await hopToOtherIsolationDomain()
}
// ❌ 同步前綴僅為 `print`(微不足道,非主執行緒);第一個 await 跳離
Task {
print("Also not main-thread-bound")
await hopToOtherIsolationDomain()
}
// ✅ 從主 actor 之外開始,僅在需要 UI 工作時跳回
Task { @concurrent in
await hopToOtherIsolationDomain()
await MainActor.run { updateUI() }
}
// ✅ 同步前綴確實包含主 actor 工作——保留繼承
Task {
print("debug") // 微不足道,非主執行緒——隨附
self.isLoading = true // 需要 @MainActor,在任何 await 之前
await fetchData()
}
Swift 6 遷移快速指南
Swift 6 的主要變更:
- 嚴格並發檢查預設啟用
- 編譯時完整的資料競爭安全
- 在邊界強制執行 Sendable 要求
- 所有非同步邊界的隔離檢查
遷移驗證循環
對每個遷移變更應用此循環:
- 建置 — 執行
swift build或 Xcode 建置以顯示新的診斷 - 修復 — 一次處理一個類別的錯誤(例如,先處理所有 Sendable 問題)
- 重建 — 確認修復方案能乾淨編譯後再繼續
- 測試 — 執行測試套件以捕捉回歸(
swift test或 Cmd+U) - 僅在所有診斷都已解決後,才繼續到下一個檔案/模組
如果修復方案引入了新的警告,請在繼續之前解決它們。永遠不要批次處理多個不相關的修復——保持提交小而可審查。
有關詳細的遷移步驟,請參閱 references/migration.md。
參考資料路由器
開啟與問題匹配的最小參考資料:
- 基礎
references/async-await-basics.md— async/await 語法、執行順序、async let、URLSession 模式references/tasks.md— Task 生命週期、取消、優先級、任務群組、結構化與非結構化references/actors.md— Actor 隔離、@MainActor、全域 actor、可重入性、自訂執行器、Mutexreferences/sendable.md— Sendable 遵循、值/參考型別、@unchecked、區域隔離references/threading.md— 執行模型、暫停點、Swift 6.2 隔離行為
- 串流
references/async-sequences.md— AsyncSequence、AsyncStream、何時使用 vs 一般 async 方法references/async-algorithms.md— Debounce、throttle、merge、combineLatest、通道、計時器
- 應用主題
references/testing.md— 優先使用 Swift Testing、XCTest 備用方案、洩漏檢查references/performance.md— 使用 Instruments 進行效能分析、減少暫停點、執行策略references/memory-management.md— 任務中的保留循環、記憶體安全模式references/core-data.md— NSManagedObject sendability、自訂執行器、隔離衝突
- 遷移與工具
references/migration.md— Swift 6 遷移策略、閉包轉 async、@preconcurrency、FRP 遷移references/linting.md— 並發相關的 lint 規則和 SwiftLintasync_without_await
- 詞彙表
references/glossary.md— 核心並發術語的快速定義
驗證檢查清單
在變更並發程式碼時:
- 在解讀診斷之前,重新檢查建置設定。
- 建置並清除一個類別的錯誤後再繼續。不要將不相關的修復批次處理到同一個變更中。
- 執行測試,特別是 actor、生命週期和取消相關的測試。
- 使用 Instruments 進行效能聲明,而不是猜測。
- 驗證長時間執行任務的釋放和取消行為。
- 在長時間執行的操作中檢查
Task.isCancelled。 - 在非同步上下文中,當 actor 隔離或
Mutex能更安全地表達所有權時,永遠不要使用 semaphore 或臨時鎖定。
注意:此技能基於 Antoine van der Lee 的全面 Swift 並發課程。






