swift-concurrency-6-2

swift-concurrency-6-2

熱門

Swift 6.2 平易近人的並行處理 — 預設單執行緒,使用 @concurrent 明確卸載到背景,為主執行緒類型提供隔離的協定遵循。

23萬星標
3.5萬分支
更新於 2026/7/17
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 的步驟:

  1. 將包含的類型標記為 nonisolated
  2. 在函式上加上 @concurrent
  3. 如果尚未是非同步,則加上 async
  4. 在呼叫端加上 await

關鍵設計決策

決策 理由
預設單執行緒 最自然的程式碼無資料競爭;並行是選擇性加入
Async 停留在呼叫的 actor 消除導致資料競爭錯誤的隱含卸載
隔離的協定遵循 MainActor 類型可以遵循協定,無需不安全的變通方法
@concurrent 明確選擇加入 背景執行是刻意的效能選擇,而非偶然
MainActor 預設推斷 減少應用程式目標中繁瑣的 @MainActor 註解
選擇性採用 非破壞性的遷移路徑 — 逐步啟用功能

遷移步驟

  1. 在 Xcode 中啟用:Swift 編譯器 > 建置設定中的並行區段
  2. 在 SPM 中啟用:在套件清單中使用 SwiftSettings API
  3. 使用遷移工具:透過 swift.org/migration 自動進行程式碼變更
  4. 從 MainActor 預設開始:為應用程式目標啟用推斷模式
  5. 在需要的地方加上 @concurrent:先進行效能分析,再卸載熱點路徑
  6. 徹底測試:資料競爭問題會變成編譯時期錯誤

最佳做法

  • 從 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 應用程式)
  • 效能最佳化 — 將特定的繁重計算卸載到背景