A Whirlwind Tour of Swift Concurrency

Stephen Dixon
· 3 min read
Send by email

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
switch
result {
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.