Swift Concurrency (非同步程式碼)
Swift 5.5 引入了全新的 Concurrency (並發) 模型,讓寫非同步程式碼變得像寫同步程式碼一樣直觀、安全且高效。這是 Swift 語言發展史上最大的變革之一。
在此之前,我們通常使用 Closure (Callbacks) 或 Combine 來處理非同步工作,但這容易導致「Callback Hell」以及難以追蹤的執行緒安全問題。
核心概念
async/await:定義與呼叫非同步函式的語法。Task:建立非同步工作的單元。Actor:保護可變狀態 (Mutable State) 當被多個執行緒同時存取時的安全性。
Async / Await
定義非同步函式
在函式參數後加上 async 關鍵字,表示這是一個非同步函式。如果有回傳值,寫在箭頭前。如果會拋出錯誤,寫 async throws。
func fetchPhoto(url: String) async throws -> UIImage {
// 模擬網路請求
try await Task.sleep(nanoseconds: 1 * 1_000_000_000) // 休息 1 秒
return UIImage(named: "photo")!
}
呼叫非同步函式
呼叫 async 函式時,必須在其前方加上 await 關鍵字。這代表程式執行到這裡會暫停 (Suspend),直到非同步工作完成後才繼續往下執行。這期間執行緒可以去處理其他工作。
func showPhoto() async {
do {
let photo = try await fetchPhoto(url: "https://example.com/image.jpg")
print("圖片下載完成")
// 更新 UI
} catch {
print("下載失敗: \(error)")
}
}
await 只能在非同步環境 (如另一個 async 函式或 Task 區塊) 中使用。結構化並發 (Structured Concurrency)
Swift 允許你並行執行多個非同步工作。
Async Let
如果你想同時下載三張圖片,而不是一張接一張下載:
async func loadGallery() {
// 這裡的三個下載任務會「同時」開始
async let firstPhoto = fetchPhoto(url: "1.jpg")
async let secondPhoto = fetchPhoto(url: "2.jpg")
async let thirdPhoto = fetchPhoto(url: "3.jpg")
// 直到這一行才會暫停,等待三張都下載完
let photos = try await [firstPhoto, secondPhoto, thirdPhoto]
print("三張圖都好了")
}
Task
如果你在同步環境 (例如 SwiftUI 的 Button action 或 UIKit 的 viewDidLoad) 想要呼叫 async 函式,你需要建立一個 Task:
// 這是在一般的同步函式中
func onButtonTap() {
Task {
// 進入非同步環境
await showPhoto()
}
}
Actors
Actor 是一種參照型別 (Reference Type),類似 Class,但它保證同一時間只有一個任務可以存取它的可變狀態。這完美解決了 Data Race (競態條件) 的問題。
定義 Actor 使用 actor 關鍵字:
actor BankAccount {
var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Double {
if balance >= amount {
balance -= amount
return amount
}
return 0
}
}
存取 Actor
因為 Actor 會確保執行緒安全,所以從外部存取它的屬性或方法時,必須使用 await (因為如果不巧有別人正在用,你需要等待)。
let account = BankAccount()
Task {
await account.deposit(amount: 100.0)
let currentBalance = await account.balance // 讀取屬性也需要 await
print(currentBalance)
}
MainActor
@MainActor 是一個全域的 Actor,代表主執行緒 (Main Thread)。在 UI 開發中,所有更新 UI 的操作都必須在主執行緒執行。
你可以將類別、屬性或方法標記為 @MainActor,編譯器就會強制確保它們只在主執行緒上執行。
@MainActor
class ViewModel: ObservableObject {
@Published var name: String = ""
func updateName(newName: String) {
self.name = newName // 安全地在 Main Thread 更新 UI 相關變數
}
}
Swift Concurrency 提供了一套現代、安全且高效的工具,徹底改變了 iOS 開發中處理非同步任務的方式。