SKILL.md
readonlyread-only
name
swift-concurrency-6-2
description
Swift 6.2 平易近人的並行處理 — 預設單執行緒,使用 @concurrent 明確卸載到背景,為主執行緒類型提供隔離的協定遵循。
Swift 6.2 平易近人的並行處理
採用 Swift 6.2 並行模型的模式,程式碼預設以單執行緒執行,並明確引入並行。消除常見的資料競爭錯誤,同時不犧牲效能。
何時啟用
- 將 Swift 5.x 或 6.0/6.1 專案遷移至 Swift 6.2
- 解決資料競爭安全的編譯器錯誤
- 設計基於 MainActor 的應用程式架構
- 將 CPU 密集型工作卸載到背景執行緒
- 在 MainActor 隔離的類型上實作協定遵循
- 在 Xcode 26 中啟用平易近人並行建置設定
核心問題:隱含的背景卸載
在 Swift 6.1 及更早版本中,async 函式可能被隱含地卸載到背景執行緒,即使在看似安全的程式碼中也可能導致資料競爭錯誤:
// Swift 6.1: 錯誤
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
// 錯誤:傳送 'self.photoProcessor' 有造成資料競爭的風險
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Swift 6.2 修正了這個問題:async 函式預設停留在呼叫的 actor 上。
// Swift 6.2: 正確 — async 停留在 MainActor 上,無資料競爭
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
核心模式 — 隔離的協定遵循
MainActor 類型現在可以安全地遵循非隔離的協定:
protocol Exportable {
func export()
}
// Swift 6.1: 錯誤 — 跨越到主執行緒隔離的程式碼
// Swift 6.2: 使用隔離協定遵循即可正確
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
編譯器確保該協定遵循僅在主執行緒上使用:
// 正確 — ImageExporter 也是 @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // 安全:相同的 actor 隔離
}
}
// 錯誤 — 非隔離的上下文無法使用 MainActor 協定遵循
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // 錯誤:此處無法使用主執行緒隔離的協定遵循
}
}
核心模式 — 全域與靜態變數
使用 MainActor 保護全域/靜態狀態:
// Swift 6.1: 錯誤 — 非 Sendable 類型可能具有共享的可變狀態
final class StickerLibrary {
static let shared: StickerLibrary = .init() // 錯誤
}
// 修正:加上 @MainActor 註解
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // 正確
}
MainActor 預設推斷模式
Swift 6.2 引入了一種模式,其中 MainActor 預設被推斷 — 無需手動註解:
// 啟用 MainActor 預設推斷後:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // 隱含為 @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // 隱含為 @MainActor
}
extension StickerModel: Exportable { // 隱含為 @MainActor 協定遵循
func export() {
photoProcessor.exportAsPNG()
}
}
此模式為選擇性啟用,建議用於應用程式、腳本及其他可執行目標。
核心模式 — 使用 @concurrent 進行背景工作
當需要真正的平行處理時,使用 @concurrent 明確卸載:
重要: 此範例需要平易近人並行建置設定 — SE-0466(MainActor 預設隔離)和 SE-0461(NonisolatedNonsendingByDefault)。啟用這些設定後,
extractSticker會停留在呼叫者的 actor 上,使得可變狀態存取安全。若無這些設定,此程式碼存在資料競爭 — 編譯器會標記它。
nonisolated final class PhotoProcessor {
private var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] {
return sticker
}
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
// 將昂貴的工作卸載到並行執行緒池
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}
// 呼叫端必須使用 await
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
使用 @concurrent 的步驟:
- 將包含的類型標記為
nonisolated - 在函式上加上
@concurrent - 如果尚未是非同步,則加上
async - 在呼叫端加上
await
關鍵設計決策
| 決策 | 理由 |
|---|---|
| 預設單執行緒 | 最自然的程式碼無資料競爭;並行是選擇性加入 |
| Async 停留在呼叫的 actor | 消除導致資料競爭錯誤的隱含卸載 |
| 隔離的協定遵循 | MainActor 類型可以遵循協定,無需不安全的變通方法 |
@concurrent 明確選擇加入 |
背景執行是刻意的效能選擇,而非偶然 |
| MainActor 預設推斷 | 減少應用程式目標中繁瑣的 @MainActor 註解 |
| 選擇性採用 | 非破壞性的遷移路徑 — 逐步啟用功能 |
遷移步驟
- 在 Xcode 中啟用:Swift 編譯器 > 建置設定中的並行區段
- 在 SPM 中啟用:在套件清單中使用
SwiftSettingsAPI - 使用遷移工具:透過 swift.org/migration 自動進行程式碼變更
- 從 MainActor 預設開始:為應用程式目標啟用推斷模式
- 在需要的地方加上
@concurrent:先進行效能分析,再卸載熱點路徑 - 徹底測試:資料競爭問題會變成編譯時期錯誤
最佳做法
- 從 MainActor 開始 — 先撰寫單執行緒程式碼,之後再最佳化
- 僅對 CPU 密集型工作使用
@concurrent— 圖片處理、壓縮、複雜計算 - 為主要為單執行緒的應用程式目標啟用 MainActor 推斷模式
- 卸載前先進行效能分析 — 使用 Instruments 找出實際瓶頸
- 使用 MainActor 保護全域變數 — 全域/靜態可變狀態需要 actor 隔離
- 使用隔離的協定遵循,而非
nonisolated變通方法或@Sendable包裝器 - 逐步遷移 — 在建置設定中一次啟用一個功能
應避免的反模式
- 對每個 async 函式都加上
@concurrent(大多數不需要背景執行) - 使用
nonisolated來壓制編譯器錯誤,而不理解隔離 - 保留舊的
DispatchQueue模式,而 actor 提供了相同的安全性 - 在與並行相關的 Foundation Models 程式碼中跳過
model.availability檢查 - 與編譯器對抗 — 如果它報告資料競爭,程式碼確實存在並行問題
- 假設所有 async 程式碼都在背景執行(Swift 6.2 預設:停留在呼叫的 actor)
何時使用
- 所有新的 Swift 6.2+ 專案(平易近人並行是建議的預設)
- 從 Swift 5.x 或 6.0/6.1 並行遷移現有應用程式
- 在採用 Xcode 26 期間解決資料競爭安全的編譯器錯誤
- 建構以 MainActor 為中心的應用程式架構(大多數 UI 應用程式)
- 效能最佳化 — 將特定的繁重計算卸載到背景






