swift-concurrency

swift-concurrency

熱門

診斷 Swift 並發問題,將基於回呼的程式碼重構為 async/await,並在處理任務、actor、@MainActor、Sendable、資料競爭、執行緒安全或與並發相關的編譯器及 linter 警告時,引導 Swift 6 遷移。

1573星標
99分支
更新於 2026/5/4
SKILL.md
readonlyread-only
name
swift-concurrency
description

診斷 Swift 並發問題,將基於回呼的程式碼重構為 async/await,並在處理任務、actor、@MainActor、Sendable、資料競爭、執行緒安全或與並發相關的編譯器及 linter 警告時,引導 Swift 6 遷移。

Swift 並發

快速路徑

在提出修復方案之前:

  1. 分析 Package.swift.pbxproj 以確定 Swift 語言模式、嚴格並發等級、預設隔離和即將推出的功能。務必執行此步驟,不僅限於遷移工作。
  2. 擷取確切的診斷資訊和違規符號。
  3. 確定隔離邊界:@MainActor、自訂 actor、actor 實例隔離或 nonisolated
  4. 確認程式碼是否繫結 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 = MainActorSWIFT_APPROACHABLE_CONCURRENCY = YES。將這些視為新建立專案的可能預設值,而非已確認的設定。

如果其中任何一項未知,請要求開發者確認後再提供遷移相關的建議。不要猜測,即使是針對新的 Xcode 26 專案。

護欄:

  • 不要將 @MainActor 作為萬能修復方案。請說明為何程式碼確實繫結 UI。
  • 優先使用結構化並發而非非結構化任務。僅在有明確理由時使用 Task.detached
  • 如果建議使用 @preconcurrency@unchecked Sendablenonisolated(unsafe),需要記錄安全不變量並制定後續移除計畫。
  • 以最小安全變更為最佳化目標。不要在遷移期間重構不相關的架構。
  • 課程參考資料僅供深入學習使用。謹慎使用,僅在確實有助於回答開發者問題時才引用。

快速修復模式

當以下所有條件成立時,使用快速修復模式:

  • 問題僅限於單一檔案或單一型別。
  • 隔離邊界明確。
  • 修復方案可以用 1-2 個保持行為的步驟解釋。

當以下任一條件成立時,跳過快速修復模式:

  • 建置設定或預設隔離未知。
  • 問題跨越模組邊界或變更公開 API 行為。
  • 可能的修復方案依賴於不安全的逃脫機制。

常見診斷

診斷 首先檢查 最小安全修復 升級至
Main actor-isolated ... cannot be used from a nonisolated context 這是否確實繫結 UI? 將呼叫者隔離至 @MainActor,或僅在主 actor 所有權正確時使用 await MainActor.run { ... } references/actors.mdreferences/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.mdreferences/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

當快速修復失敗時

  1. 如果尚未確認,請收集專案設定。
  2. 重新評估型別跨越了哪些隔離邊界。
  3. 導向匹配的參考檔案以取得更深入的修復方案。
  4. 如果修復方案可能改變行為,請記錄不變量並新增驗證步驟。

最小安全修復

偏好能在滿足資料競爭安全的同時保留行為的變更:

  • UI 繫結狀態:將型別或成員隔離至 @MainActor
  • 共享可變狀態:將其移至 actor 後方,或僅在狀態為 UI 所擁有時使用 @MainActor
  • 背景工作:當工作必須跳離呼叫者隔離時,使用標記為 @concurrentasync API;當工作可以安全地繼承呼叫者隔離時,使用不帶 @concurrentnonisolated。在產生 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 要求
  • 所有非同步邊界的隔離檢查

遷移驗證循環

對每個遷移變更應用此循環:

  1. 建置 — 執行 swift build 或 Xcode 建置以顯示新的診斷
  2. 修復 — 一次處理一個類別的錯誤(例如,先處理所有 Sendable 問題)
  3. 重建 — 確認修復方案能乾淨編譯後再繼續
  4. 測試 — 執行測試套件以捕捉回歸(swift test 或 Cmd+U)
  5. 僅在所有診斷都已解決後,才繼續到下一個檔案/模組

如果修復方案引入了新的警告,請在繼續之前解決它們。永遠不要批次處理多個不相關的修復——保持提交小而可審查。

有關詳細的遷移步驟,請參閱 references/migration.md

參考資料路由器

開啟與問題匹配的最小參考資料:

  • 基礎
    • references/async-await-basics.md — async/await 語法、執行順序、async let、URLSession 模式
    • references/tasks.md — Task 生命週期、取消、優先級、任務群組、結構化與非結構化
    • references/actors.md — Actor 隔離、@MainActor、全域 actor、可重入性、自訂執行器、Mutex
    • references/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 規則和 SwiftLint async_without_await
  • 詞彙表
    • references/glossary.md — 核心並發術語的快速定義

驗證檢查清單

在變更並發程式碼時:

  1. 在解讀診斷之前,重新檢查建置設定。
  2. 建置並清除一個類別的錯誤後再繼續。不要將不相關的修復批次處理到同一個變更中。
  3. 執行測試,特別是 actor、生命週期和取消相關的測試。
  4. 使用 Instruments 進行效能聲明,而不是猜測。
  5. 驗證長時間執行任務的釋放和取消行為。
  6. 在長時間執行的操作中檢查 Task.isCancelled
  7. 在非同步上下文中,當 actor 隔離或 Mutex 能更安全地表達所有權時,永遠不要使用 semaphore 或臨時鎖定。

注意:此技能基於 Antoine van der Lee 的全面 Swift 並發課程