A Whirlwind Tour of Swift Concurrency
So, I started writing a guide on how to implement an authorization layer in our apps using Firebase Authentication — and before I knew it, I had written well over a thousand words on just the basics of Swift Concurrency.
It occurred to me that it might be worthwhile spending some time taking a whirlwind tour of Swift Concurrency so that it might set us up for success as we all begin to adopt Swift 6.
If you were to take away just a few things Swift Concurrency offers us, they are:
- Improved code readability and maintainability
- Much safer and more efficient multithreading
- Easier ways to manage tasks and cancellation
There are more benefits, but this is a whirlwind tour — so let’s get into it.
Swift Concurrency in a Nutshell
Let’s step back and look at what concurrency means. Imagine a keynote is happening in one room while a hands-on workshop runs simultaneously in another. These events are concurrent — happening at the same time, independently.
Now bring that into Swift, and we’re talking about running multiple things side by side in our app. Swift Concurrency gives us tools to manage this easily and safely.
In one of my apps, when a user taps Save on their profile, two tasks run in parallel: their profile picture is sent to Firebase Storage, and their profile info is saved to Firestore. Both uploads happen at the same time. When the image upload finishes, the app updates the Firestore document with the image URL — all without blocking the user.
Swift Concurrency makes this seamless.
Improved Code Readability and Maintainability
Swift Concurrency helps us write more readable, maintainable code using async
and await
. Before this model, we relied heavily on completion handlers and callbacks, which could easily spiral into unmanageable code.
Before:
fetchData { result in
result {
switch case .success(let
data):
processData(data) case .failure(let
error): print("Error: \(error)
")
}
}
After:
let data = try await
fetchData()
processData(data)
The async/await syntax reduces noise, improves clarity, and makes intent easier to follow.
Much Safer and More Efficient Multithreading
Concurrency brings the risk of race conditions and inconsistent state. Swift Concurrency solves this with actors — reference types that isolate state and ensure safe, single-threaded access to data.
Example:
actor Counter
{ var value = 0
() {
func increment value += 1
}
}
Actors:
- Guarantee exclusive access to their data
- Replace manual synchronization (like
DispatchQueue
) - Prevent race conditions by serializing access
Usage:
let counter = Counter
()Task
{ await
counter.increment() print(await
counter.value)
}
Using await
here ensures the function cooperates with the concurrency system — waiting its turn if needed.
Easier Task Management and Cancellation
Swift makes it easy to cancel tasks cooperatively. Cancellation doesn’t force-stop your function — instead, it sets a flag. Your task checks this flag and decides when to exit.
Example:
func loadImage() async throws -> UIImage
{ guard !Task.isCancelled else { throw CancellationError
() } // Fetch image
}
Tasks should check Task.isCancelled
at key moments to avoid unnecessary work.
Full Example:
let imageLoadTask = Task
{ do
{ let image = try await
loadImage() print("Image loaded:"
, image) } catch is CancellationError
{ print("Image load cancelled."
) } catch
{ print("An error occurred:"
, error)
}
}
imageLoadTask.cancel()
This model provides fine-grained control, avoids forced shutdowns, and ensures predictable state cleanup.
Cancellation in Long Tasks
You can add cancellation checks throughout longer-running operations.
func loadImage() async throws -> UIImage
{ guard !Task.isCancelled else { throw CancellationError
() } let imageData = try await
fetchData() guard !Task.isCancelled else { throw CancellationError
() } let image = UIImage(data: imageData) ?? UIImage
() guard !Task.isCancelled else { throw CancellationError
() } return
image
}
Each step checks the cancellation flag, allowing early exits.
Why It Matters
Swift's approach to cooperative cancellation has key benefits:
- Efficient resource usage: Avoid wasting CPU and memory on canceled work
- Predictable behavior: Graceful exits instead of forced shutdowns
- Improved UX: Respond quickly when users move on
- Clear error handling: Differentiate cancellations from actual failures
Let’s Recap
Swift Concurrency gives us:
async/await
for readable asynchronous code- Actors for safe and scalable multithreading
- Structured task management with built-in cancellation
- Cleaner, safer, more scalable code for modern apps
It’s the concurrency model Apple is betting on — and it’s the one we should lean into.
This post was a whirlwind intro to Swift Concurrency — from async/await and actors to cooperative cancellation and safer multithreading. I’ll be diving deeper into these patterns (and how I use them in real projects like AteIQ) in future posts.
If you’re exploring this space too, I’d genuinely love to connect or you can drop me a line.
And if you’re just getting started, I hope this blog becomes a place you can revisit and grow alongside.
Until next time — write clear code, and let Swift do the hard stuff.