Post

What is a Checked Continuation in Swift?

What is a Checked Continuation in Swift?

What is a Checked Continuation in Swift?

A checked continuation in Swift is a mechanism provided by Swift Concurrency to bridge closure-based asynchronous code into the structured async/await world. It ensures that an async function can return a value or throw an error only once, helping prevent common concurrency issues like double-resumption or forgetting to resume.


Types of Checked Continuations

Swift provides two types of checked continuations:

1️⃣ withCheckedContinuation
2️⃣ withCheckedThrowingContinuation


The Problem: Callback Hell in Legacy APIs

Imagine this: You’re working on an iOS app that fetches user data from a remote API. But the API you’re dealing with still uses completion handlers. Your code looks something like this:

1
2
3
4
5
6
7
8
9
10
func fetchUserData(completion: @escaping (Result<String, Error>) -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
        let success = Bool.random()
        if success {
            completion(.success("User Data Fetched"))
        } else {
            completion(.failure(NSError(domain: "FetchError", code: 1)))
        }
    }
}

Not only does this trap us in callback hell, but error handling gets messier as the project scales.
Wouldn’t it be great if we could use async/await instead?


The Solution: Bridging Callbacks with Checked Continuations

Swift’s withCheckedThrowingContinuation lets us transform legacy completion-based APIs into async/await with just a few lines of code.

Here’s how we can rewrite fetchUserData() using checked continuations:

1
2
3
4
5
6
7
8
9
10
11
12
func fetchUserData() async throws -> String {
    return try await withCheckedThrowingContinuation { continuation in
        fetchUserData { result in
            switch result {
            case .success(let data):
                continuation.resume(returning: data)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

Now, when we call this function, it looks cleaner and more readable:

1
2
3
4
5
6
7
8
Task {
    do {
        let userData = try await fetchUserData()
        print("✅ Success:", userData)
    } catch {
        print("❌ Error:", error)
    }
}

✅ No more nested callbacks.
✅ No more state confusion.
✅ Just clean, structured concurrency!


Real-World Use Case: API Calls, Database Fetching, and More

Checked continuations are a lifesaver when working with:

  • ✅ Networking APIs (URLSession, Firebase, Alamofire)
  • ✅ Database queries (CoreData, Realm, SQLite)
  • ✅ Bluetooth communication (CoreBluetooth)
  • ✅ Third-party SDKs with legacy callbacks

For example, in CoreData:

1
2
3
4
5
6
7
8
9
10
func fetchCoreDataEntity() async throws -> [UserEntity] {
    return try await withCheckedThrowingContinuation { continuation in
        do {
            let users = try context.fetch(UserEntity.fetchRequest())
            continuation.resume(returning: users)
        } catch {
            continuation.resume(throwing: error)
        }
    }
}

This lets you write code that’s safer, easier to debug, and scales well with Swift’s structured concurrency.


✅ Key Takeaways

1️⃣ Checked Continuations prevent double resumption
→ No more accidental crashes!

2️⃣ They bridge old APIs to async/await
→ Making Swift code more readable and modern.

3️⃣ Debugging is safer
→ Swift will warn you if you forget to resume a continuation.


If you’re still using completion handlers in your iOS app, it’s time to upgrade to async/await with checked continuations.


🚀 Have you used withCheckedThrowingContinuation before?
What challenges did you face when migrating to async/await?

🎉 Happy Swifting! 🚀

Follow me in LinkedIn for more informative posts.

This post is licensed under CC BY 4.0 by the author.
Step Bytes
raw 38344
endings 38192
comments 35402
collapse 29191
clippings 28957