2025 年 7 月 4 日
iOS iPadOS macOS Swift tvOS visionOS watchOS WWDC WWDC25從單執行緒到並行架構:深度解構 Swift Concurrency 的開發演進指南
為什麼當代開發者必須重新審視並行運算?
在行動裝置硬體效能過剩的時代,處理器的多核心架構早已普及,然而開發者面臨的核心挑戰已從單純的「運算速度」轉向「回應品質」。高延遲操作(如網路請求或大型檔案讀寫)若處理不當,將直接阻塞主執行緒,導致 UI 掉幀甚至系統掛起。
Swift Concurrency 的出現並非僅是語法上的微調,而是一場開發思維的「典範轉移」。它將並行運算的安全性從開發者的自由心證,轉化為編譯器的強大防禦。對於資深架構師而言,這不僅是為了解決當前的卡頓,更是為了建構一個具備彈性、可維護且在編譯期即具備資料競爭安全性(Data-race Safety)的系統。這場演進之旅,必須從理解單執行緒模型的穩定性開始。
起點:Main Actor 模式與單執行緒的安全堡壘
對於多數應用程式而言,維持單執行緒的簡單性是初期的最佳戰略。正如 WWDC 中所言:「單執行緒程式碼更容易編寫與維護,因為它在同一時間只做一件事。」
Main Actor 的戰略意義
在最新的開發環境中(如預計於 Xcode 26 起預設開啟的機制),Apple 引入了「Approachable Concurrency」設定。這是一套即將到來的特性組合,其核心在於「預設主執行緒隔離」。這意味著編譯器會隱含地為模組內的所有內容加上 @MainActor 標記。
- 編譯器寫好的安全:開發者不再需要手動為每個 UI 元件標註屬性。在 Main Actor 模式下,全域靜態變數與 UI 狀態天生受到保護,免於複雜的鎖機制(Locks)帶來的死鎖風險
- 適用範圍:此模式是主應用模組與 UI 互動模組的理想選擇,讓開發者在尚未引入複雜並行邏輯前,能擁有絕對的「運行時安全」
然而,單執行緒的堡壘在面對外部網路請求時會顯得力不從心。若在主執行緒執行同步網路 API,UI 將會立即凍結直至資料下載完成。
異步轉換:利用 Async/Await 破解 UI 阻塞困境
為了解決高延遲操作,我們必須引入「掛起點 Suspension Points」的概念。async/await 的優勢在於它能在不增加程式碼複雜度的情況下,優雅地釋放主執行緒。
交錯執行(Interleaving)的效能優勢
當我們調用 URLSession.shared.data(from:) 並標註 await 時,函式會在此掛起。這不只是「不阻塞」,更關鍵的是實現了「交錯執行」:
- 資源利用最大化:在等待網路資料期間,執行緒不會閒置,而是能立即轉向處理其他已就緒的任務(例如同時獲取新聞列表)
- 效率邏輯:系統能確保執行緒隨時在有進度的地方運作,而非乾等單一操作
// 同步版本:會阻塞主執行緒直至下載完成
func fetchImageSync(url: URL) -> UIImage { ... }
// 異步版本:建立掛起點,允許主執行緒在下載期間處理 UI 事件
func fetchImage(url: URL) async throws -> UIImage {
// 系統在此掛起,釋放執行緒供其他任務交錯執行
let (data, _) = try await URLSession.shared.data(from: url)
return UIImage(data: data)!
}
在此階段,雖然我們調用了異步 API,但我們自定義的邏輯(如圖片解碼)可能仍運行在主執行緒上。當面對「計算密集型」任務時,我們需要進一步將工作卸載至背景。
效能卸載:導入 @concurrent 將重度運算移至背景
作為架構師,我們的目標是將 CPU 密集型工作(如大型圖像解碼)移出主執行緒。判斷標準很簡單:如果一項工作無法透過優化演算法變得更快,且其耗時已足以造成 UI 掉幀,就必須導入並行運算。
@concurrent 與執行緒切換
透過 @concurrent 屬性,我們顯性地告知 Swift 該函式應在背景執行緒池(Concurrent Thread Pool)中運行。這會觸發編譯器的安全性檢查,強制函式斷開與主執行緒隔離狀態的聯繫。
- 戰略選擇:在開發通用函式庫時,
nonisolated往往是更靈活的選擇。它允許調用者自行決定執行地點,從主執行緒調用即留在主執行緒,從背景調用則留在背景 - 系統調度:系統會根據設備硬體(如 Apple Watch 的雙核或 Mac 的多核)自動管理執行緒池。當任務在背景掛起並恢復時,它可能會切換到池中的另一個執行緒,而這一切都由 Swift Concurrency 安全地隔離
核心防禦:Sendable 協議與編譯期的資料競爭防護
當資料開始在不同執行緒間流動,開發者將面對並行開發的終極考驗:資料競爭(Data Race)。Swift 透過 Sendable 協議將「運行時的焦慮」轉化為「編譯期的信心」。
「貓咪圖片」案例:為什麼需要 Sendable?
考慮一個典型的崩潰情境:
- 主執行緒持有原始圖片並準備顯示
- 同時,背景任務啟動一個 Task 試圖縮放該圖片,並修改其寬高與像素陣列
- 主執行緒正在讀取像素以進行渲染,而背景執行緒正在寫入。 結果: 由於存取越界,程式直接崩潰
// 錯誤示範:非 Sendable 的 Image 類別被 Task 捕獲
func scaleAndDisplay(image: ImageClass) {
Task { // 這裡會引發編譯器錯誤,因為 ImageClass 不是 Sendable
image.scale(to: 0.5) // 背景修改
await display(image) // 回傳主執行緒
}
display(image) // 主執行緒同時讀取 -> Data Race!
}
戰略建議:值類型與隔離
- 天生安全:
URL、Data、String以及struct等值類型是天生Sendable的,因為它們在傳輸時是獨立副本 - 隱含安全性:值得注意的是,所有標註為
@MainActor的類型均隱含符合Sendable - 非 Sendable 類別:對於資料模型,架構上的最佳實踐是將其保留在
@MainActor或保持 non-Sendable。這能防止開發者試圖用複雜的鎖機制去同步類別,而是強制透過隔離來確保安全
架構進階:自定義 Actor 實現解耦的狀態管理
當應用規模擴大,過多非 UI 邏輯(如網路狀態管理)堆積在 Main Actor 會導致「主執行緒爭用 Contention」。此時,任務會頻繁地「跳回 Hop」主執行緒,造成微小的 UI 延遲。
自定義 Actor 的價值
透過引入獨立的 NetworkManager actor,我們可以將管理連線狀態(如存取 open connections 字典)的工作徹底移出主執行緒。
- 緩解爭用:Actor 確保其內部的可變狀態同一時間僅由一個任務存取,且運行在背景中,避免了大量非 UI 任務排隊等待主執行緒的情況
- 架構分層:
- UI 曾與模型層:建議保留在
@MainActor - 子系統層(網路、快取):應封裝在自定義
actor中 - 工具函式:保持
nonisolated以獲取最大靈活性
現代 Swift 開發者的並行演進藍圖
Swift Concurrency 不是一次性的技術導入,而是一個隨著應用複雜度提升而逐步採用的工具集。從單執行緒起步是明智的,而後的每一步演進——從 async/await 隱藏延遲,到 @concurrent 卸載運算,最後到 actor 隔離狀態,都是為了在高效能與安全性之間取得平衡。
延伸思考:
在你的現有專案中,有哪些運算任務(如大型 JSON 解析、本地資料庫過濾)正悄悄侵蝕著主執行緒的流暢度?這些任務是否可以透過 nonisolated 標記或移至背景 Actor 來優化?
建議:
立即在專案中啟用 Approachable Concurrency 設定,並利用 Xcode 的編譯警示來排查潛在的資料競爭。請參考「Swift 6 遷移指南」,從編譯器的提示中學習如何建構一個真正數據安全、流暢響應的現代 iOS 應用。
關於 XcodeProject
XcodeProject 創立於 2023,致力於協助開發者探索 Apple 的創新世界,學習在 iOS、iPadOS、macOS、tvOS、visionOS 與 watchOS 上開發 App,發現眾多技術與框架,讓開發者獲得更多能力。