01 · INTRODUCTION
What Are the Benefits of Swift Concurrency?
Swift Concurrency gives iOS developers a safer, more structured and more scalable way to organise asynchronous and concurrent work. It helps applications remain responsive, reduces the amount of manual thread and queue management we need to perform, gives mutable state explicit isolation, allows the compiler to detect many concurrency mistakes, and gives asynchronous work a structure through Tasks, child Tasks, cancellation and error propagation.
The biggest benefit is therefore not that Swift Concurrency simply makes code execute faster.
It changes how we architect work.
Instead of constructing applications around threads, callbacks, queues, locks and conventions that every developer must remember, we can increasingly describe:
CODE EXAMPLE
What work needs to happen?
What work can happen concurrently?
What state belongs together?
Who owns that mutable state?
Where may a Task suspend?
Which operations depend on one another?
When should unfinished work be cancelled?
What values may safely cross
between concurrency domains?
Swift and the Swift Concurrency Runtime can then help enforce those decisions.
💡 The Big Benefit
Swift Concurrency moves concurrency from a collection of manually enforced threading techniques toward a language-level model of Tasks, suspension, structured lifetimes and isolated state.
The result can be code that is easier to understand, easier to maintain and substantially harder to make accidentally unsafe.
02 · TUTORIAL
Swift Concurrency Gives Us a Better Abstraction Than Threads
Before Swift Concurrency, iOS developers frequently discussed asynchronous work in terms of threads and queues.
We might say:
CODE EXAMPLE
Move this to a background queue.
Dispatch this back to main.
Protect this property with a serial queue.
Create another operation.
Add a lock here.
Those techniques remain part of the history and foundations of concurrent programming, and lower-level threads still exist underneath Swift Concurrency.
But a thread is an operating-system execution resource. It is not necessarily the best abstraction for describing the architecture of one of our application features.
When we load a profile, download ten images or refresh three APIs, what we actually have is work.
Swift Concurrency lets us describe that work as Tasks and asynchronous functions instead of requiring the application architecture to revolve around creating and managing threads directly.
EXECUTION DIAGRAM
OLD MENTAL MODEL
Application Work
│
▼
Which Queue?
│
▼
Which Thread?
│
▼
How Do I Synchronize It?
SWIFT CONCURRENCY
Application Work
│
▼
Task / async operation
│
▼
Isolation + Structure
│
▼
Swift Concurrency Runtime
│
▼
Underlying Threads
The threads have not disappeared.
We have simply gained a much better level at which to design our applications.
03 · TUTORIAL
Benefit: Asynchronous Code Can Read From Top to Bottom
One of the most immediately visible improvements is the way asynchronous code can be written.
A callback-based API might require us to write:
CODE EXAMPLE
api.loadLaunches { result in
switch result {
case .success(let launches):
DispatchQueue.main.async {
self.launches = launches
}
case .failure(let error):
DispatchQueue.main.async {
self.errorMessage = error.localizedDescription
}
}
}
Nothing about this code is inherently broken.
But the logical operation has been split across closures, result handling and dispatch calls.
With an async API, the same operation can often be expressed more directly:
CODE EXAMPLE
@MainActor
func refresh() async {
do {
launches = try await api.loadLaunches()
} catch {
errorMessage = error.localizedDescription
}
}
The code now reads much more closely to the actual intention:
EXECUTION DIAGRAM
load launches
↓
wait for result
↓
store launches
or
receive error
↓
store error state
This is not merely prettier syntax.
Readable execution structure makes asynchronous behaviour easier to reason about during development, debugging and code review.
04 · TUTORIAL
Benefit: Waiting Does Not Need to Mean Blocking a Thread
This is one of the most important architectural advantages.
Imagine:
CODE EXAMPLE
let launches = try await api.loadLaunches()
The network request may take half a second.
Our processor does not need to execute half a second of instructions to make a remote server respond.
The operation spends much of its lifetime waiting.
With Swift's asynchronous Task model, the current Task can potentially suspend while it waits.
EXECUTION DIAGRAM
Task A
████████
│
▼
await
│
▼
SUSPEND
⋮
⋮
result ready
│
▼
Task A continues
████████
The Task remains unfinished.
But a system thread does not need to remain permanently dedicated to that suspended Task merely to represent the fact that the operation has not completed yet.
This allows underlying execution resources to be used by other eligible work. Swift's concurrency APIs are built around Tasks, executors and jobs rather than a one-Task-to-one-thread model. citeturn754324search0turn754324search8
05 · TUTORIAL
Benefit: Our UI Can Remain Responsive While Work Continues
For an iOS developer, this is one of the benefits the user can actually feel.
At 60 frames per second, a frame opportunity occurs approximately every 16.7 milliseconds.
A synchronous operation that monopolises main-thread execution for 200 milliseconds spans roughly twelve 60 Hz frame intervals.
Even if the calculation itself is impressively fast when measured in isolation, the application can feel terrible.
Swift Concurrency gives us a model in which asynchronous work can remain unfinished without requiring MainActor execution to remain blocked for the entire lifetime of that operation.
EXECUTION DIAGRAM
CONCEPTUAL MODEL
TIME ─────────────────────────────────────────▶
Refresh Task
████
│
▼
SUSPEND
⋮
⋮
⋮
████
resume
MainActor / UI Work
██ ██ ██ ██ ██
The Task can make progress.
The UI can make progress.
Other Tasks can make progress.
This is much closer to the kind of concurrency we actually want in an interactive application.
06 · TUTORIAL
Benefit: Concurrency Becomes About Progress Rather Than Hogging an Execution Resource
Consider a large piece of application work.
CODE EXAMPLE
for item in items {
process(item)
}
Perhaps the complete computation takes 200 milliseconds.
The old instinct can easily become:
CODE EXAMPLE
I have execution now.
Finish absolutely everything.
A concurrent application should instead make us ask:
CODE EXAMPLE
Does all of this work
need to finish before anything
else may make progress?
Sometimes it does.
Often it does not.
Where an algorithm permits it, large workloads can be architected as meaningful units of work that cooperate with other work in the application.
EXECUTION DIAGRAM
CONCEPTUAL MODEL
Task A
████ ████ ████
Task B
████ ████
UI Work
██ ██ ██
Swift Concurrency does not automatically chop arbitrary synchronous code into time slices for us.
But it gives us an execution model in which Tasks, suspension and explicit cooperative mechanisms make this style of architecture possible.
07 · TUTORIAL
Benefit: Many Tasks Do Not Require Many Dedicated Threads
Imagine an application with one thousand asynchronous operations.
A primitive model might imply:
CODE EXAMPLE
1000 asynchronous operations
=
1000 system threads
That would be an expensive relationship.
Threads require operating-system resources, stack space and scheduling overhead. Creating more threads also does not create more CPU cores.
Swift Tasks are not system threads.
That lets many logical asynchronous operations exist while the runtime coordinates runnable work over the available underlying execution resources.
EXECUTION DIAGRAM
Task 1
Task 2
Task 3
Task 4
Task 5
...
Task 1000
│
▼
Swift Concurrency Runtime
│
▼
Executors / Jobs
│
▼
Managed System Threads
│
▼
Finite CPU Cores
This separation between how much work exists and how many threads exist is one of the foundations of the system.
08 · TUTORIAL
Benefit: Swift Can Protect Shared Mutable State With Actors
One of the hardest problems in concurrent programming has always been shared mutable state.
Consider:
CODE EXAMPLE
final class Counter {
var value = 0
func increment() {
value += 1
}
}
If multiple execution contexts mutate value concurrently without synchronization, we can create a data race.
Swift gives us actors:
CODE EXAMPLE
actor Counter {
private(set) var value = 0
func increment() {
value += 1
}
}
The actor establishes an isolation domain around its mutable state.
Actor-isolated execution is serialized so that multiple actor-isolated jobs do not execute simultaneously against the same actor instance. The Swift language documentation describes actors as allowing only one Task at a time to access their mutable state. citeturn754324search6
EXECUTION DIAGRAM
Task A ─────┐
│
Task B ─────┼────▶ Counter Actor
│ │
Task C ─────┘ ▼
Serial Execution
│
▼
value
Instead of manually inventing a synchronization convention around every mutable resource, we can express the ownership directly in Swift.
09 · TUTORIAL
Benefit: Safety Rules Move From Team Memory Into the Language
This is a very significant improvement for commercial code bases.
Before actors, a team might establish a rule such as:
CODE EXAMPLE
Never access UserStore directly.
Always go through
userStoreQueue.
That can be a perfectly reasonable design.
But the rule may live in documentation, comments, code review and the memories of experienced developers.
A new developer can accidentally bypass it.
With actor isolation, Swift understands the isolation boundary.
EXECUTION DIAGRAM
OLD
Shared State
│
▼
Serial Queue / Lock
│
▼
Developer Convention
NEW
Actor State
│
▼
Actor Isolation
│
▼
Compiler-checked access rules
This means part of the concurrency architecture becomes something the compiler can reason about rather than an informal agreement the team hopes everybody remembers.
10 · TUTORIAL
Benefit: Swift Can Detect Many Data-Race Risks Before the App Runs
This is one of Swift Concurrency's strongest benefits.
Historically, concurrency bugs could be extremely difficult to reproduce.
A race might happen once in a thousand executions.
It might happen only on one device.
It might disappear when logging is added because the timing changes.
Swift's isolation rules and strict concurrency checking allow many unsafe transfers and isolation violations to become compiler diagnostics rather than intermittent runtime behaviour.
Swift 6's concurrency checking is specifically designed to provide stronger protection against data races in concurrent Swift code. Apple's concurrency documentation includes migration guidance for adopting the full concurrency protection of Swift 6 language mode. citeturn754324search0turn754324search1
This does not mean Swift can prove that all of our business logic is correct.
It means an important category of concurrency correctness has moved closer to compile time.
11 · TUTORIAL
Benefit: Sendable Makes Crossing Concurrency Boundaries Explicit
Once we isolate state, another question appears:
What values may safely move between those isolation domains?
That is where Sendable becomes important.
CODE EXAMPLE
struct Launch: Sendable {
let id: Int
let name: String
}
A sendable value can safely cross concurrency boundaries according to Swift's sendability rules.
EXECUTION DIAGRAM
Isolation Domain A
Launch
│
▼
============================
CONCURRENCY BOUNDARY
============================
│
▼
Isolation Domain B
Apple describes Sendable as representing values that can be shared across concurrent contexts without introducing a risk of data races. Its semantic requirements are checked by the compiler. citeturn754324search2
Again, the language is being given information that previously existed largely in architecture diagrams and developer assumptions.
12 · TUTORIAL
Benefit: MainActor Gives UI State a Clear Owner
Every iOS developer knows the old rule:
CODE EXAMPLE
Update UI on the main thread.
With Swift Concurrency, we can express UI-facing ownership as isolation.
CODE EXAMPLE
@MainActor
final class LaunchModel {
var launches: [Launch] = []
var isLoading = false
var errorMessage: String?
}
This says something much stronger than:
CODE EXAMPLE
Remember to call
DispatchQueue.main.async
whenever these properties change.
The state itself belongs to the MainActor isolation domain.
Apple defines MainActor as a global singleton actor whose executor is equivalent to the main dispatch queue. citeturn754324search5
This gives UI-facing mutable state an explicit concurrency owner.
13 · TUTORIAL
Benefit: Structured Concurrency Gives Tasks Lifetimes
Concurrency becomes much easier to reason about when asynchronous work has structure.
Imagine a refresh operation needs launches and rockets:
CODE EXAMPLE
func loadDashboard() async throws -> Dashboard {
async let launches = api.loadLaunches()
async let rockets = api.loadRockets()
return try await Dashboard(
launches: launches,
rockets: rockets
)
}
The two child operations belong to the surrounding asynchronous operation.
Conceptually:
EXECUTION DIAGRAM
loadDashboard()
│
├──── child: loadLaunches()
│
└──── child: loadRockets()
│
▼
wait for required children
│
▼
return Dashboard
This is different from launching unrelated background operations whose lifetimes are disconnected from the function that needed them.
Structured concurrency gives our asynchronous code a tree-shaped lifetime.
Parents and children have relationships.
Those relationships give Swift more information about when work belongs, when it should finish, and how errors and cancellation can propagate.
14 · TUTORIAL
Benefit: Fixed Concurrent Work Can Be Expressed Directly With async let
Suppose we need three independent pieces of data:
CODE EXAMPLE
async let launches = api.loadLaunches()
async let rockets = api.loadRockets()
async let astronauts = api.loadAstronauts()
let result = try await (
launches,
rockets,
astronauts
)
We can express that the operations may progress concurrently without manually creating queues, dispatch groups and callback bookkeeping.
EXECUTION DIAGRAM
loadLaunches() ──────────────┐
│
loadRockets() ───────────────┼──▶ await results
│
loadAstronauts() ────────────┘
This makes the concurrency relationship visible in the structure of the Swift code itself.
15 · TUTORIAL
Benefit: Dynamic Concurrent Work Can Be Structured With Task Groups
Sometimes we do not know the number of operations until runtime.
Imagine loading details for a collection of missions.
CODE EXAMPLE
func loadDetails(
for ids: [Int]
) async throws -> [MissionDetail] {
try await withThrowingTaskGroup(
of: MissionDetail.self
) { group in
for id in ids {
group.addTask {
try await api.loadMission(id: id)
}
}
var details: [MissionDetail] = []
for try await detail in group {
details.append(detail)
}
return details
}
}
The work can progress concurrently while remaining inside a structured scope.
When the scope finishes, its child-Task relationship has a defined lifetime rather than becoming an uncontrolled collection of background operations.
Apple exposes Task groups specifically for dynamically created child Tasks within a scope. citeturn754324search0
16 · TUTORIAL
Benefit: Errors Fit Naturally Into Async Code
Swift's existing error model works naturally with asynchronous functions.
CODE EXAMPLE
func refresh() async throws {
launches = try await api.loadLaunches()
}
We do not need a separate concurrency-specific error system.
The same language constructs remain useful:
CODE EXAMPLE
do {
try await refresh()
} catch {
print(error)
}
And with structured child Tasks, errors can participate in the structure of the surrounding operation instead of being manually routed through deeply nested callback chains.
17 · TUTORIAL
Benefit: Cancellation Becomes Part of the Task Model
Application work often becomes irrelevant before it finishes.
The user leaves the screen.
A new search query replaces the previous one.
A parent operation fails.
The application no longer needs a result.
Swift Tasks have a cancellation model.
CODE EXAMPLE
func processLaunches(
_ launches: [Launch]
) async throws {
for launch in launches {
try Task.checkCancellation()
process(launch)
}
}
Cancellation is cooperative rather than a mechanism that blindly destroys arbitrary execution halfway through an instruction sequence.
That gives application code an opportunity to stop doing work that no longer matters while retaining control over cleanup and correctness.
18 · TUTORIAL
Benefit: Parent and Child Work Can Share Cancellation
Structured concurrency makes cancellation especially useful because child work belongs to a larger operation.
EXECUTION DIAGRAM
Parent Task
│
├── Child A
├── Child B
└── Child C
If the parent operation no longer makes sense, its structured children are not simply mysterious detached operations floating around somewhere in the application.
The structure gives the runtime and our code a meaningful relationship through which cancellation can propagate.
This is a significant improvement over concurrency designs where developers must manually track every outstanding callback or operation token.
19 · TUTORIAL
Benefit: Concurrent Code Can Express Ownership
One of the deepest benefits is that Swift Concurrency encourages us to ask:
CODE EXAMPLE
Who owns this state?
rather than merely:
CODE EXAMPLE
Which queue should I use?
Consider:
CODE EXAMPLE
actor LaunchStore {
private var launches: [Launch] = []
func replace(with launches: [Launch]) {
self.launches = launches
}
}
The actor tells us where the state belongs.
That ownership becomes part of the program's concurrency architecture.
This is easier to reason about than a large application containing dozens of unrelated mutable properties with synchronization policies scattered across queues, locks and comments.
20 · TUTORIAL
Benefit: Isolation Can Exist at the Correct Architectural Level
Different parts of an application need different isolation domains.
UI-facing state may belong to MainActor:
CODE EXAMPLE
@MainActor
final class LaunchModel {
var launches: [Launch] = []
}
A mutable cache might belong to its own actor:
CODE EXAMPLE
actor LaunchCache {
private var cache: [Int: Launch] = [:]
}
Another immutable value may need no actor at all:
CODE EXAMPLE
struct LaunchID: Sendable {
let value: Int
}
We do not need to force one global synchronization mechanism across the entire application.
We can model ownership around the actual architecture of the product.
21 · TUTORIAL
Benefit: Independent Actors Can Still Make Progress Concurrently
An actor serialises isolated jobs for one actor instance.
That does not mean every actor throughout the application shares one giant serial queue.
EXECUTION DIAGRAM
Actor A
Job A1
████████
Job A2
████████
Actor B
Job B1
█████████
Job B2
█████████
Each actor protects its own isolation domain while the wider application remains concurrent.
This gives us a useful combination:
CODE EXAMPLE
CONCURRENCY
Many independent pieces
of work can progress
+
ISOLATION
Mutable state that must not
be accessed simultaneously
can be protected
That combination is much closer to what large applications actually need.
22 · TUTORIAL
Benefit: Actor Suspension Does Not Require Holding the Actor Forever
An asynchronous actor method can suspend.
CODE EXAMPLE
actor LaunchStore {
private var launches: [Launch] = []
func refresh() async throws {
let result = try await api.loadLaunches()
launches = result
}
}
If the Task suspends at await, the actor does not need to remain uselessly reserved for that entire network wait.
EXECUTION DIAGRAM
Task A — Actor Job
████████
│
▼
SUSPEND
Task B — Actor Job
████████
Task A Continuation
████████
Other eligible actor work can make progress while the original Task is suspended.
The actor retains serial execution of isolated jobs without forcing one asynchronous Task to monopolise the actor for its entire lifetime.
23 · TUTORIAL
Benefit: Swift Concurrency Gives the Compiler More Information
This may be one of the most important benefits when working in a team.
Compare these two architectures:
CODE EXAMPLE
Architecture A
"Everybody knows this property
must only be touched from queue X."
Architecture B
"This type is @MainActor."
"This state belongs to this actor."
"This value is Sendable."
The second architecture contains concurrency information in declarations the compiler understands.
This improves documentation, code review and correctness simultaneously.
A developer joining the project does not need to discover every concurrency rule solely by reading old implementation details.
24 · TUTORIAL
Benefit: Concurrency Intent Becomes Visible in Function Signatures
Consider:
CODE EXAMPLE
func loadLaunches() async throws -> [Launch]
Without opening the implementation, we already know several useful things.
The operation is asynchronous.
It can throw.
Calling it from asynchronous code requires acknowledging the potential suspension point with await.
Now consider:
CODE EXAMPLE
@MainActor
func update(with launches: [Launch])
The isolation requirement is also visible.
Concurrency behaviour is becoming part of the API contract instead of being hidden inside implementation details such as:
CODE EXAMPLE
DispatchQueue.main.async { ... }
25 · TUTORIAL
Benefit: await Makes Suspension Boundaries Visible
This deserves particular attention.
When we see:
CODE EXAMPLE
let launches = try await api.loadLaunches()
the source code visibly tells us:
CODE EXAMPLE
Execution may suspend here.
That matters because time may pass.
Other permitted work may execute.
Actor state may legitimately change.
The operation may be cancelled.
By making suspension points visible, Swift gives developers an important place to stop and reconsider assumptions.
26 · TUTORIAL
Benefit: The Compiler Can Help Us Find Isolation Mistakes During Refactoring
Imagine a large application whose concurrency architecture exists mainly as GCD conventions.
Moving a property, changing a callback or adding another execution path can accidentally bypass the established synchronization design.
With actor isolation, MainActor annotations and sendability requirements, changing the architecture can cause compiler errors at the places whose assumptions are no longer valid.
This can make large refactors safer because the concurrency relationships are represented in the type and isolation system rather than only in runtime behaviour.
27 · TUTORIAL
Benefit: Code Review Becomes More About Architecture Than Thread Guessing
Imagine reviewing old concurrent code:
CODE EXAMPLE
queue.async {
service.load { result in
otherQueue.async {
cache.write(result)
DispatchQueue.main.async {
completion(result)
}
}
}
}
A reviewer has to reconstruct the execution model.
Which queue are we currently on?
Is otherQueue serial?
Can something else access the cache?
Does completion always execute on main?
Swift Concurrency does not eliminate all reasoning, but it lets more of those rules become explicit:
CODE EXAMPLE
@MainActor
final class LaunchModel {
private let cache: LaunchCache
func refresh() async throws {
let launches = try await api.loadLaunches()
await cache.store(launches)
self.launches = launches
}
}
The reviewer can reason in terms of isolation and asynchronous boundaries instead of reverse-engineering a web of queues.
28 · TUTORIAL
Benefit: Swift Concurrency Scales Better With Modern Hardware
Modern Apple devices contain multiple processor cores.
Where work is independent and suitable for concurrent execution, the runtime and operating system can make use of available execution resources and parallel hardware.
EXECUTION DIAGRAM
CONCEPTUAL MODEL
Task A ─────▶ eligible work ─────▶ Core 1
Task B ─────▶ eligible work ─────▶ Core 2
Task C ─────▶ eligible work ─────▶ Core 3
But the important benefit is that application architecture does not need to map every Task manually to a physical core or dedicated thread.
Swift works with executors and Tasks, while the OS remains responsible for lower-level thread scheduling.
This separation lets the runtime adapt execution to the available system resources rather than hard-coding one thread strategy into every feature.
29 · TUTORIAL
Benefit: Concurrency Can Improve Throughput
Suppose three independent requests each take approximately one second.
A purely sequential implementation could behave conceptually like:
EXECUTION DIAGRAM
Request A
████████████
Request B
████████████
Request C
████████████
Total elapsed time:
approximately 3 seconds
If the operations are genuinely independent and can progress concurrently:
EXECUTION DIAGRAM
Request A
████████████
Request B
████████████
Request C
████████████
Elapsed time can approach
the longest individual operation
rather than the sum of all three.
This is one of the practical performance benefits of concurrency.
But we should be precise:
💡 Concurrency Does Not Automatically Mean Faster
Concurrency can improve responsiveness and throughput when independent work overlaps effectively.
It does not guarantee that every concurrent implementation finishes faster than every sequential implementation.
Coordination has costs, dependencies may force sequential execution, and CPU-heavy workloads remain constrained by available hardware.
30 · TUTORIAL
Benefit: We Can Choose Sequential or Concurrent Execution Explicitly
Not everything should be concurrent.
Sometimes operation B genuinely depends on operation A:
CODE EXAMPLE
let user = try await api.loadUser()
let orders = try await api.loadOrders(for: user.id)
This dependency is naturally sequential.
Other operations may be independent:
CODE EXAMPLE
async let profile = api.loadProfile()
async let news = api.loadNews()
Swift lets the structure of the source code communicate the difference.
This makes concurrency an intentional architectural decision rather than an accidental consequence of which queue happened to receive a closure.
31 · TUTORIAL
Benefit: The Code Can Better Describe the Product
This benefit is easy to underestimate.
Imagine a product requirement:
CODE EXAMPLE
When the dashboard opens:
Load launches
Load rockets
Load astronaut information
Allow independent requests
to progress concurrently.
If the user leaves,
cancel unnecessary work.
Update UI state safely.
Swift Concurrency gives us language constructs that directly correspond to those requirements.
Tasks represent work.
Structured child Tasks represent relationships.
Cancellation represents abandoned work.
Actors represent isolated ownership.
MainActor represents UI-facing isolation.
Sendable describes values that can safely cross concurrency domains.
The concurrency architecture can therefore begin to resemble the product architecture.
32 · TUTORIAL
Benefit: A Complete Feature Can Have One Coherent Concurrency Story
Consider a small application-shaped example.
CODE EXAMPLE
struct Launch: Sendable {
let id: Int
let name: String
}
actor LaunchCache {
private var launches: [Launch] = []
func store(_ launches: [Launch]) {
self.launches = launches
}
}
@MainActor
final class LaunchModel {
private let api: LaunchAPI
private let cache: LaunchCache
private(set) var launches: [Launch] = []
private(set) var isLoading = false
private(set) var errorMessage: String?
init(
api: LaunchAPI,
cache: LaunchCache
) {
self.api = api
self.cache = cache
}
func refresh() async {
isLoading = true
errorMessage = nil
defer {
isLoading = false
}
do {
let launches = try await api.loadLaunches()
try Task.checkCancellation()
await cache.store(launches)
self.launches = launches
} catch is CancellationError {
return
} catch {
errorMessage = error.localizedDescription
}
}
}
Look at how much architecture is visible.
Launch is suitable for movement across concurrency boundaries.
LaunchCache owns mutable cached state.
LaunchModel owns UI-facing state on MainActor.
refresh() is asynchronous.
The network operation can suspend.
Cancellation is acknowledged.
The cache update crosses into another actor's isolation.
The final UI mutation remains MainActor-isolated.
This is the kind of architecture Swift Concurrency makes possible without requiring the feature to be designed primarily as a set of queues and thread transitions.
33 · TUTORIAL
Benefit: Migration Can Be Incremental
Commercial applications are rarely rewritten from zero.
Most iOS developers are working inside products that already contain years of callbacks, delegates, GCD queues, operation queues and third-party libraries.
Swift Concurrency does not require every historical API to disappear on day one.
Async code can coexist with existing APIs, and continuations provide mechanisms for bridging callback-based operations into async functions where appropriate.
This makes adoption a migration problem rather than necessarily a complete rewrite.
34 · TUTORIAL
Benefit: Concurrency Knowledge Becomes Transferable Across Features
Once your application uses common Swift Concurrency concepts consistently, developers do not need to relearn an entirely custom synchronization system for every feature.
The same vocabulary appears throughout the project:
CODE EXAMPLE
Task
async
await
actor
@MainActor
Sendable
structured concurrency
cancellation
isolation
executor
A developer who understands those contracts can move between networking, persistence, UI state and background processing with a common mental model.
This reduces architectural noise.
35 · TUTORIAL
Benefit: Concurrency Becomes Easier to Teach
This may sound like a softer benefit, but it matters enormously on professional teams.
Compare teaching a new developer:
CODE EXAMPLE
This class is mostly accessed
from queue A.
Except this callback comes
from queue B.
Then we bounce onto main.
But this property has its own lock.
Do not call method C from queue D.
with:
CODE EXAMPLE
This model is @MainActor.
This cache is an actor.
These values are Sendable.
This async function can suspend.
These child Tasks belong
to this parent operation.
The second architecture still requires knowledge.
But the knowledge is based on shared Swift language concepts rather than an undocumented concurrency system invented by one code base.
36 · TUTORIAL
Benefit: Concurrency Rules Become Searchable and Reviewable
This sounds trivial until you work on an application with millions of lines of code.
You can search for:
CODE EXAMPLE
@MainActor
actor
Sendable
Task {
Task.detached
async let
withTaskGroup
and discover meaningful pieces of the application's concurrency architecture.
That is much harder when concurrency ownership is encoded only through arbitrary queue variable names and unwritten team conventions.
37 · TUTORIAL
Benefit: Swift Concurrency Creates a Common Architecture Across Apple Platforms
Swift Concurrency is a language-level model rather than an iPhone-only concurrency trick.
The same core concepts can be used across Swift applications and Apple platforms:
CODE EXAMPLE
Tasks
async / await
Actors
MainActor
Sendable
Structured concurrency
Cancellation
This gives developers a more consistent concurrency vocabulary as applications expand across iPhone, iPad, Mac, Apple Watch, Apple TV, visionOS and server-side Swift environments.
38 · TUTORIAL
Benefit: The Runtime Can Manage Execution Better Than Individual Features
One application feature rarely has enough information to know the ideal number of threads for the entire device.
A developer working on the image loader should not need to decide:
CODE EXAMPLE
This feature needs exactly 14 threads.
while another developer independently decides:
CODE EXAMPLE
The database needs 12 more.
and another framework creates another pool.
Swift's concurrency runtime and the operating system can coordinate execution resources at levels with much broader knowledge of available work and hardware.
The application can describe the work instead of every feature attempting to become its own scheduler.
39 · TUTORIAL
Benefit: We Can Separate Safety From Performance
This is another major conceptual improvement.
Historically, developers can easily combine two different questions:
CODE EXAMPLE
Is this state safe?
and
Where should this expensive
calculation execute?
Swift Concurrency lets us reason about them separately.
Actor isolation can answer:
CODE EXAMPLE
Who safely owns this state?
while execution architecture answers:
CODE EXAMPLE
Where should this CPU-heavy work run
so it does not delay MainActor?
An actor does not make expensive work inexpensive.
MainActor does not make code fast.
Sendable does not make something asynchronous.
Separating these contracts gives us a much cleaner architecture.
40 · TUTORIAL
Benefit: Modern Swift Can Make Unsafe Assumptions Visible
Moving an existing code base toward strict concurrency often initially produces compiler complaints.
That can feel frustrating.
But many of those diagnostics reveal assumptions that previously existed without being formally checked.
For example:
CODE EXAMPLE
This mutable reference type
is being transferred between
concurrency domains.
Is that actually safe?
or:
CODE EXAMPLE
This property belongs to MainActor.
Why is this nonisolated code
trying to access it directly?
The compiler is forcing the architecture to answer questions that the old code may simply have ignored.
41 · TUTORIAL
Benefit: Fewer Timing Bugs Have to Be Discovered by Users
This is ultimately where concurrency safety matters.
A data race is not an academic inconvenience.
It can produce:
CODE EXAMPLE
lost state
incorrect account information
duplicated updates
missing values
crashes
corrupted caches
inconsistent UI state
rare production-only failures
When Swift can make more concurrency mistakes impossible or diagnosable before release, fewer bugs have to be discovered after a customer happens to trigger exactly the wrong execution timing.
That is a direct product-quality benefit.
42 · TUTORIAL
Benefit: Concurrency Becomes Part of API Design
Look at this API:
CODE EXAMPLE
@MainActor
protocol LaunchPresenting {
func refresh() async throws
}
The API communicates far more than a callback-based signature that hides its execution contract.
It communicates isolation.
It communicates asynchrony.
It communicates error behaviour.
Concurrency decisions become part of how APIs are designed rather than being an invisible implementation detail discovered only after reading the source.
43 · TUTORIAL
Benefit: Swift Concurrency Gives Us One Mental Model
This might be the largest benefit of all.
Without a coherent model, iOS concurrency can feel like dozens of unrelated rules:
CODE EXAMPLE
Do not block the main thread.
Use a background queue.
Task is not a thread.
await does not mean background.
Actors serialize state.
MainActor owns UI work.
Sendable crosses boundaries.
Task groups create child Tasks.
Cancellation is cooperative.
But once Swift Concurrency is understood as one system, these statements connect.
EXECUTION DIAGRAM
APPLICATION WORK
│
▼
TASKS
│
▼
async / await
│
├──── suspension
│
├──── cancellation
│
├──── child Tasks
│
└──── errors
│
▼
ISOLATION
│
├──── actors
├──── MainActor
└──── Sendable
│
▼
EXECUTORS
│
▼
SYSTEM THREADS
│
▼
OS SCHEDULER
│
▼
CPU CORES
Now we have one architecture rather than a collection of warnings.
44 · TUTORIAL
What Swift Concurrency Does Not Promise
A good list of benefits also needs boundaries.
Swift Concurrency does not make every operation faster.
It does not make every operation parallel.
It does not automatically move CPU-heavy code away from MainActor.
It does not automatically divide long synchronous loops into tiny cooperative pieces.
It does not make every logical race condition impossible.
It does not remove operating-system threads.
It does not mean developers can stop understanding execution, ownership and architecture.
Swift Concurrency gives us better tools and stronger language guarantees.
We still have to design the application correctly.
45 · TUTORIAL
The Real Benefit Is Cooperative Architecture
Return to the model we have been developing throughout this documentation.
An application contains many things that need to make progress:
CODE EXAMPLE
UI rendering
User input
Network requests
Image loading
Database work
Caching
Animations
Account refresh
Search
Background computation
The goal should not be for one feature to acquire an execution resource and then prevent everything else from progressing until it has completed all possible work.
A better architecture asks:
💡 How Can Everything Make Progress?
Which work can execute concurrently?
Which work must remain sequential?
Where can Tasks suspend?
Which mutable state must be isolated?
Which values may cross those boundaries?
Which work should be cancelled when it is no longer useful?
This is the mindset behind modern cooperative concurrency.
46 · TUTORIAL
A Useful Before and After
We can summarise the transition like this:
CODE EXAMPLE
BEFORE
Threads
Queues
Locks
Callbacks
Dispatch groups
Manual state synchronization
Team conventions
Timing assumptions
SWIFT CONCURRENCY
Tasks
async / await
Suspension
Structured child Tasks
Cancellation
Actors
MainActor
Sendable
Compiler-checked isolation
Executors
The old mechanisms still exist beneath and alongside the new system.
Swift Concurrency does not erase computing history.
It gives application developers a more useful abstraction above it.
47 · TUTORIAL
What to Remember
💡 What to Remember
Swift Concurrency gives Swift a structured, language-level model for asynchronous and concurrent application work.
Tasks let us represent asynchronous work without requiring one dedicated system thread per operation.
async and await allow asynchronous code to read much more like ordinary sequential Swift.
A Task can suspend while waiting rather than requiring a thread to remain blocked for the entire asynchronous lifetime of the operation.
This helps interactive applications remain responsive while other work continues making progress.
Actors give mutable state explicit isolation and serialize actor-isolated execution.
MainActor gives UI-facing mutable state a clear main execution owner.
Sendable gives Swift information about values that can safely cross concurrency domains.
Strict concurrency checking allows many isolation and data-race risks to become compiler diagnostics instead of intermittent runtime failures.
Structured concurrency gives asynchronous child work meaningful parent-child lifetimes.
async let provides a direct way to express a fixed amount of independent concurrent work.
Task groups provide structure for a dynamic number of child Tasks.
Task cancellation lets work cooperate when its result is no longer needed.
Errors fit naturally into async functions through Swift's existing throws, try and catch model.
Swift Concurrency reduces the need for individual features to manually manage threads, queues and synchronization mechanisms.
Concurrency intent becomes visible in function signatures, type declarations and isolation annotations.
The runtime can coordinate many Tasks over a smaller collection of underlying execution resources.
Independent work can overlap and may use multiple CPU cores when parallel execution is available and appropriate.
Concurrency does not guarantee faster execution, and it does not automatically make expensive synchronous work cooperative.
The biggest architectural change is that we can increasingly describe work, ownership, isolation, lifetime and dependencies rather than manually describing threads.
48 · TUTORIAL
Your Next Move
The benefits of Swift Concurrency become much easier to appreciate once we stop treating it as a collection of new keywords.
async is useful because functions can participate in asynchronous execution.
await is useful because suspension points become explicit.
Tasks are useful because asynchronous work no longer needs to be represented as a dedicated thread.
Actors are useful because mutable state can have an owner.
MainActor is useful because UI-facing state can have a clear isolation domain.
Sendable is useful because values crossing concurrency boundaries can be checked.
Structured concurrency is useful because asynchronous work has relationships and lifetimes.
And the Swift Concurrency Runtime ties those concepts together into one execution model.
That means the next step is not simply to memorise more syntax.
The next step is to learn each of those parts well enough that you can look at an existing iOS application and answer:
How should this application be structured so that all of its important work can make progress safely and cooperatively?
That is the journey through Swift Concurrency.
