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:錯誤 — 跨越到 main actor 隔離的程式碼
// Swift 6.2:正確,使用隔離符合
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
編譯器確保此符合僅在 main actor 上使用:
// 正確 — 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 隔離的符合
}
}
核心模式 — 全域與靜態變數
使用 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 編譯器 > 建置設定中的 Concurrency 區段
- 在 SPM 中啟用:在套件清單中使用
SwiftSettingsAPI - 使用遷移工具:透過 swift.org/migration 進行自動程式碼變更
- 從 MainActor 預設值開始:為應用程式目標啟用推斷模式
- 在需要的地方新增
@concurrent:先進行效能分析,然後卸載熱門路徑 - 徹底測試:資料競爭問題會變成編譯時期錯誤
最佳實踐
- 從 MainActor 開始 — 先編寫單執行緒程式碼,之後再最佳化
- 僅對 CPU 密集型工作使用
@concurrent— 影像處理、壓縮、複雜計算 - 為主要單執行緒的應用程式目標啟用 MainActor 推斷模式
- 在卸載前進行效能分析 — 使用 Instruments 找出實際瓶頸
- 使用 MainActor 保護全域變數 — 全域/靜態可變狀態需要 actor 隔離
- 使用隔離符合,而不是
nonisolated解決方法或@Sendable包裝器 - 逐步遷移 — 在建置設定中一次啟用一個功能
應避免的反模式
- 對每個 async 函式套用
@concurrent(大多數不需要背景執行) - 使用
nonisolated來壓制編譯器錯誤,而不了解隔離 - 當 actor 提供相同安全性時,仍保留舊的
DispatchQueue模式 - 跳過並行相關 Foundation 模型程式碼中的
model.availability檢查 - 與編譯器對抗 — 如果它報告資料競爭,表示程式碼確實存在並行問題
- 假設所有 async 程式碼都在背景執行(Swift 6.2 預設:保留在呼叫方的 actor 上)
何時使用
- 所有新的 Swift 6.2+ 專案(可近式並行是建議的預設值)
- 將現有應用程式從 Swift 5.x 或 6.0/6.1 並行模型遷移
- 在採用 Xcode 26 期間解決資料競爭安全的編譯器錯誤
- 建置以 MainActor 為中心的應用程式架構(大多數 UI 應用程式)
- 效能最佳化 — 將特定的繁重計算卸載到背景