Swift 6.2 可親近的並行處理
採用 Swift 6.2 並行處理模型的模式,程式碼預設以單執行緒執行,並明確引入並行處理。消除常見的資料競爭錯誤,同時不犧牲效能。
何時啟動
- 將 Swift 5.x 或 6.0/6.1 專案遷移到 Swift 6.2
- 解決資料競爭安全編譯器錯誤
- 設計基於 MainActor 的應用程式架構
- 將 CPU 密集型工作卸載到背景執行緒
- 在 MainActor 隔離的型別上實作協定一致性
- 在 Xcode 26 中啟用「可親近的並行處理」建置設定
核心問題:隱含的背景卸載
在 Swift 6.1 及更早版本中,非同步函數可能被隱含地卸載到背景執行緒,即使在看似安全的程式碼中也可能導致資料競爭錯誤:
// 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 修正了此問題:非同步函數預設會停留在呼叫的參與者(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()
}
}
編譯器確保此一致性僅在主執行緒上使用:
// 正常 — ImageExporter 也是 @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // 安全:相同的參與者隔離
}
}
// 錯誤 — 非隔離上下文無法使用 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會停留在呼叫端的參與者上,從而確保可變狀態存取安全。若未啟用這些設定,此程式碼存在資料競爭 — 編譯器將會標記它。
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 停留在呼叫端參與者 | 消除導致資料競爭錯誤的隱含卸載 |
| 隔離一致性 | MainActor 型別可以符合協定,無需不安全的變通方法 |
@concurrent 明確選擇加入 | 背景執行是刻意的效能選擇,而非意外 |
| MainActor 預設推斷 | 減少應用程式目標的 @MainActor 樣板標註 |
| 選擇性加入採用 | 非破壞性遷移路徑 — 逐步啟用功能 |
遷移步驟
- 在 Xcode 中啟用:建置設定中的 Swift 編譯器 > 並行處理章節
- 在 SPM 中啟用:在套件清單中使用
SwiftSettingsAPI - 使用遷移工具:透過 swift.org/migration 進行自動程式碼變更
- 從 MainActor 預設值開始:為應用程式目標啟用推斷模式
- 在需要的地方新增
@concurrent:先進行效能分析,再卸載熱點路徑 - 徹底測試:資料競爭問題將變為編譯時期錯誤
最佳實踐
- 從 MainActor 開始 — 先撰寫單執行緒程式碼,之後再最佳化
- 僅針對 CPU 密集型工作使用
@concurrent— 影像處理、壓縮、複雜計算 - 為主要為單執行緒的應用程式目標啟用 MainActor 推斷模式
- 卸載前先進行效能分析 — 使用 Instruments 找到實際瓶頸
- 使用 MainActor 保護全域變數 — 全域/靜態可變狀態需要參與者隔離
- 使用隔離一致性 而非
nonisolated變通方法或@Sendable包裝器 - 逐步遷移 — 在建置設定中一次啟用一個功能
應避免的反模式
- 對每個非同步函數都使用
@concurrent(大多數不需要背景執行) - 在不了解隔離的情況下使用
nonisolated抑制編譯器錯誤 - 當參與者能提供相同安全性時,仍保留舊的
DispatchQueue模式 - 在與並行相關的 Foundation Models 程式碼中跳過
model.availability檢查 - 與編譯器對抗 — 如果它報告資料競爭,則程式碼確實存在並行問題
- 假設所有非同步程式碼都在背景執行(Swift 6.2 預設:停留在呼叫端參與者)
何時使用
- 所有新的 Swift 6.2+ 專案(「可親近的並行處理」是建議的預設值)
- 從 Swift 5.x 或 6.0/6.1 並行處理遷移現有應用程式
- 在採用 Xcode 26 期間解決資料競爭安全編譯器錯誤
- 建構以 MainActor 為中心的應用程式架構(大多數 UI 應用程式)
- 效能最佳化 — 將特定重度計算卸載到背景