Skip to main content

Swift 中的 resume 用法整理

在 Swift 中,resume 主要出現在兩個場景:URLSessionTaskContinuation。這篇文章幫你釐清兩者的差異和正確用法。

resume 的觀念如下圖,因為過去的 fetch 沒有 async await ,所以在現在有 async await 的 function 中如果有去 call fetch 的東西,我們會需要知道說,是否 fetch 已經完成了。所以需要透過調用 resume 才會知道說完成了,這個時候 async 的 function 才會結束 task 不然, task 就會一直被掛著,這時 swift 就會有 error log 出現。

 ## 類似這種
SWIFT TASK CONTINUATION MISUSE: sendHeartbeat(with:) leaked its continuation without resuming it. This may cause tasks waiting on it to remain suspended forever.

📋 目錄

  1. URLSessionTask 的 resume()
  2. Continuation 的 resume()
  3. 常見錯誤與注意事項
  4. 完整實例:包裝 URLSession 為 async/await
  5. 總結對比

1. URLSessionTask 的 resume()

基本用法

let url = URL(string: "https://api.example.com/data")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
print("收到資料: \(data)")
} else if let error = error {
print("錯誤: \(error)")
}
}
task.resume() // ⚠️ 必須呼叫才會開始請求

為什麼需要 resume()

  • URLSession.dataTask() 建立的是暫停狀態的任務
  • 只有呼叫 .resume() 才會真正發送網路請求
  • 忘記呼叫 = 請求永遠不會發送

進階用法:取消任務

let task = URLSession.shared.dataTask(with: url) { data, response, error in
// 處理回應
}

// 開始請求
task.resume()

// 如果需要取消
task.cancel()

2. Continuation 的 resume()

基本概念

當你要把 callback-based API 包裝成 async/await 時,會用到 withCheckedContinuation

func fetchUserData() async throws -> User {
try await withCheckedThrowingContinuation { continuation in
// 呼叫舊的 callback API
oldAPICall { result, error in
if let result = result {
continuation.resume(returning: result) // ✅ 成功
} else if let error = error {
continuation.resume(throwing: error) // ❌ 失敗
}
}
}
}

不同類型的 Continuation

// 1. 不回傳值
func performAction() async {
await withCheckedContinuation { continuation in
someAction {
continuation.resume() // 只喚醒,不回傳值
}
}
}

// 2. 回傳值
func fetchString() async -> String {
await withCheckedContinuation { continuation in
someAPI { result in
continuation.resume(returning: result)
}
}
}

// 3. 可能拋出錯誤
func fetchData() async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
someAPI { result, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: result)
}
}
}
}

3. 常見錯誤與注意事項

⚠️ 重要規則

  1. Continuation 只能 resume() 一次

    // ❌ 錯誤:會造成 crash
    continuation.resume(returning: "first")
    continuation.resume(returning: "second") // 💥 SWIFT TASK CONTINUATION MISUSE
  2. 必須呼叫 resume()

    // ❌ 錯誤:await 會永遠等待
    func badExample() async -> String {
    await withCheckedContinuation { continuation in
    // 忘記呼叫 resume() → 永遠卡住
    }
    }
  3. URLSessionTask 必須 resume()

    // ❌ 錯誤:請求不會發送
    let task = URLSession.shared.dataTask(with: url) { ... }
    // 忘記 task.resume() → 請求永遠不會發送

最佳實踐

// ✅ 正確:使用 defer 確保 resume 被呼叫
func safeFetch() async throws -> String {
try await withCheckedThrowingContinuation { continuation in
defer {
// 如果沒有其他 resume 被呼叫,這裡會處理
}

someAPI { result, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: result)
}
}
}
}

4. 完整實例:包裝 URLSession 為 async/await

// 舊的 callback 方式
func fetchDataOld(completion: @escaping (Result<Data, Error>) -> Void) {
let url = URL(string: "https://api.example.com/data")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}
task.resume() // URLSessionTask 的 resume
}

// 新的 async/await 包裝
func fetchDataNew() async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
let url = URL(string: "https://api.example.com/data")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
continuation.resume(throwing: error) // Continuation 的 resume
} else if let data = data {
continuation.resume(returning: data) // Continuation 的 resume
}
}
task.resume() // URLSessionTask 的 resume
}
}

// 使用方式
func useNewAPI() async {
do {
let data = try await fetchDataNew()
print("成功取得資料: \(data)")
} catch {
print("錯誤: \(error)")
}
}

5. 總結對比

類型用途呼叫時機注意事項
URLSessionTask.resume()啟動網路請求建立 task 後立即呼叫不呼叫 = 請求不發送
Continuation.resume()喚醒 await 等待callback 完成時呼叫只能呼叫一次
Task {}Swift Concurrency建立即自動執行不需要 resume()

記憶口訣

  • URLSessionTask:建立後要 resume() 才會發送
  • Continuation:callback 完成時要 resume() 喚醒 await
  • Swift Task:建立就自動跑,不用 resume()

🎯 實用技巧

  1. 使用 withCheckedContinuation 而不是 withUnsafeContinuation(更安全)
  2. URLSessionTask 記得 resume(),否則請求不會發送
  3. Continuation 只能 resume() 一次,重複呼叫會 crash
  4. 考慮使用 defer 來確保 continuation 一定會被 resume