01 · INTRODUCTION
How Do I Learn Swift Concurrency?
The best way to learn Swift Concurrency is not to begin with async, await, Task or actors. Those are important, but they make far more sense once you understand the system they were designed to improve.
If you are already working as an iOS developer, you have probably experienced the same problem that many of us have. You joined a codebase that already contained dispatch queues, completion handlers, background work, main-thread updates, locks, callbacks and years of accumulated decisions. You learned where to place new code, but you were rarely given the time to stop and rebuild the execution model in your head from the processor upward.
Swift Concurrency gives us a good reason to do that now.
Treat the subject like a curriculum. Write down the topics below and work through them deliberately. Do not simply memorize a new collection of keywords. Learn why the old system became difficult, what Swift is changing, what it is not changing, and how your code ultimately becomes work that has to execute on a processor.
That is the route into Swift Concurrency.
02 · TUTORIAL
Start With Concurrency and Parallelism
The first two words to understand are concurrency and parallelism.
They are related, but they do not mean the same thing.
Concurrency means that multiple pieces of work can make progress during the same period of time. Parallelism means that multiple pieces of work are literally executing at the same instant on different processor cores.
For the purpose of learning concurrency, it is useful to temporarily imagine that our processor contains only one core. Modern devices contain several cores, but adding those cores too early can hide the most important idea.
Imagine one processor core and three threads that all contain work to execute.
EXECUTION DIAGRAM
Thread 1 ████ ████
Thread 2 ████ ████
Thread 3 ████ ████
───────────────────────▶ time
Only one stream of machine instructions can execute on that single core at a particular instant. The operating system can nevertheless switch between runnable threads very quickly, allowing each thread to make some progress.
That is concurrency.
Now imagine two processor cores.
EXECUTION DIAGRAM
Core 1 ███████████████████
Core 2 ███████████████████
───────────────────▶ time
Two streams of instructions can now execute at the same instant.
That is parallelism.
💡 Important Terminology
Concurrency means multiple operations can make progress during overlapping periods of time.
Parallelism means multiple operations are literally executing simultaneously on different processor cores.
This distinction is important because Swift Concurrency is primarily about how asynchronous work is structured, scheduled and allowed to make progress. It is not a promise that every Task will run simultaneously on another CPU core.
For learning purposes, keep returning to the single-core model. If you can understand how several operations make progress on one core, you understand the heart of concurrency.
03 · TUTORIAL
Follow Your Swift Code Down to the Processor
One of the most useful things an iOS developer can learn is where their Swift code eventually goes.
We write something simple:
CODE EXAMPLE
counter += 1
But the processor does not execute Swift source code.
Your Swift program is compiled into lower-level instructions that eventually become machine instructions the processor can execute. At the physical level, computers represent information through electrical states that we abstract into binary values such as 0 and 1.
You do not need to become an electrical engineer to understand Swift Concurrency. You do, however, benefit enormously from understanding the chain between the code you type and the hardware that executes it.
A useful simplified model is:
EXECUTION DIAGRAM
Swift source code
↓
Compiled machine instructions
↓
Application process
↓
System threads
↓
Operating-system scheduling
↓
Processor cores
That model gives all of our concurrency terminology somewhere to live.
The words thread, main thread, scheduler, task, executor, suspension and actor isolation stop being disconnected definitions. They become different layers in one execution system.
This is the kind of knowledge that is increasingly valuable because many developers now learn from frameworks downward. They know how to use an API, but they may never have been required to explain what happens underneath it.
Learning Swift Concurrency is a good opportunity to reverse that trend.
04 · TUTORIAL
Learn What a Process Is
When your iOS application starts running, the operating system creates a process for it.
For our learning model, think of a process as the operating-system container for your running application. It owns an address space for the application's memory and contains the threads through which executable instructions can run.
Draw it.
EXECUTION DIAGRAM
┌─────────────────────────────────────┐
│ MY APP'S PROCESS │
│ │
│ Application memory │
│ │
│ Main thread ─────────────────▶ │
│ Thread 2 ─────────────────▶ │
│ Thread 3 ─────────────────▶ │
│ │
└─────────────────────────────────────┘
The diagram is intentionally simple, but it gives you a useful visual model.
Your application is not itself a thread. The app runs inside a process, and that process contains execution threads.
When the application begins, one of those threads is the main thread.
This is the thread iOS developers have been talking about for years.
05 · TUTORIAL
Understand the Main Thread Properly
Developers say “main thread” almost every day, but many of us learned the rule before we learned the architecture.
We were told:
Do not block the main thread.
That rule is correct, but it becomes much more useful once you can explain what the main thread actually is.
The main thread is the application's primary system thread. It is associated with the application's main execution path and, on Apple UI frameworks, with the work that must occur in the main UI execution context.
If you perform expensive synchronous work there, other work that depends on that execution context has to wait.
Imagine:
EXECUTION DIAGRAM
MAIN THREAD
Handle interaction
↓
████████████ expensive synchronous work ████████████
↓
UI work continues
The application appears frozen because the work that should update or respond to the interface cannot progress until the synchronous work finishes.
This is not a mysterious UIKit or SwiftUI problem.
The execution resource is busy.
Once you understand that, “do not block the main thread” stops being a rule you memorized and becomes a consequence you can predict.
06 · TUTORIAL
Why Did We Add More Threads?
Now run a thought experiment.
Imagine that an application process had one execution thread and no mechanism for moving long-running work elsewhere.
Everything would have to use that one execution stream.
EXECUTION DIAGRAM
Main thread
──────────────────────────────────────────────▶
If one feature started an expensive operation, other work that depended on that thread would wait.
The industry therefore needed more than one execution context.
Threads gave us that ability.
EXECUTION DIAGRAM
PROCESS
Main thread ─────────────────────────────────▶
Thread 2 ─────────────────────────────────▶
Thread 3 ─────────────────────────────────▶
The operating system can schedule runnable threads, and on a multi-core processor some may genuinely execute in parallel. On a single-core processor, the OS can switch between them so that several operations still make progress concurrently.
This was an enormous improvement.
It also created the problems that every serious concurrency system has to deal with.
07 · TUTORIAL
Grand Central Dispatch Made This Easier
Most iOS developers did not spend their careers manually creating and destroying threads.
Apple gave us Grand Central Dispatch.
With GCD, we submit work to queues and allow the system to coordinate the underlying execution resources.
For example:
CODE EXAMPLE
DispatchQueue.global().async {
performExpensiveWork()
}
There is an important point here.
DispatchQueue.global().async does not mean “create a new thread.”
It means that we submit work asynchronously to a global concurrent dispatch queue. GCD and the operating system decide how that work is executed using the available system resources.
Likewise:
CODE EXAMPLE
DispatchQueue.main.async {
updateInterface()
}
submits work to the main dispatch queue, which is associated with the application's main-thread execution.
GCD gave developers a much better abstraction than manually managing threads. But the codebase could still become full of decisions about queues, shared values, callbacks and synchronisation.
That is the world many of us inherited when we joined commercial iOS teams.
08 · TUTORIAL
The Real Problem Begins With Shared Mutable State
Multiple threads allow multiple operations to make progress.
The difficult question is what happens when those operations access the same mutable state.
Consider:
CODE EXAMPLE
var counter = 0
Now imagine thousands of operations performing:
CODE EXAMPLE
counter += 1
That line looks innocent because Swift presents it as one statement.
At the processor level, however, the conceptual work is closer to:
CODE EXAMPLE
READ counter
ADD 1
WRITE counter
The precise machine instructions depend on compilation and architecture, but the important point is that your Swift statement should not be treated as one automatically atomic operation.
Now place two execution threads around that shared value.
EXECUTION DIAGRAM
THREAD A THREAD B
READ counter → 41
READ counter → 41
ADD 1 → 42
ADD 1 → 42
WRITE 42
WRITE 42
Two pieces of code attempted to increment the counter.
The value increased only once.
One update has effectively been lost.
This is the moment where concurrency stops being an abstract subject and becomes a correctness problem.
09 · TUTORIAL
Run the Counter Experiment Yourself
If you want to learn concurrency properly, do not only read about race conditions.
Write broken code deliberately.
A small experiment can show you why shared mutable state becomes dangerous.
CODE EXAMPLE
import Foundation
final class Counter {
var value = 0
}
let counter = Counter()
let group = DispatchGroup()
for _ in 0..<3_000 {
group.enter()
DispatchQueue.global().async {
counter.value += 1
group.leave()
}
}
group.wait()
print("Expected: 3000")
print("Actual: \(counter.value)")
This program deliberately performs unsynchronised concurrent access to mutable state. You should not rely on its output, because the program does not provide a valid synchronisation mechanism around the read-modify-write operation.
Run it repeatedly.
Increase the iteration count.
Change the workload.
Watch how something that looked perfectly reasonable at the Swift source level becomes unsafe once several execution contexts can interleave their work.
This is an excellent way to learn because the problem stops being a definition in a book.
You have seen it.
10 · TUTORIAL
Learn to Think in Interwoven Instructions
One of the most useful mental models you can develop is that other work may occur between operations you mentally grouped together.
Consider:
CODE EXAMPLE
if balance > 0 {
withdraw()
}
The human reading the code sees a clear sequence.
First check the balance.
Then withdraw.
But if the state is shared and another execution context can modify it, the assumption may already be wrong by the time withdraw() executes.
EXECUTION DIAGRAM
YOUR CODE OTHER WORK
check balance
│
│ change balance
│ │
▼ ▼
withdraw using an
assumption that may
no longer be true
The same idea applies at an even lower level.
One line of Swift can compile into several lower-level instructions. The operating system does not understand your visual source-code grouping and does not promise to preserve the assumptions you made simply because two operations appear beside one another in the editor.
💡 Important Idea
Other work can happen between the operations you mentally grouped together.
This is why concurrency bugs can feel surprising.
Your Swift looks sequential.
The system is concurrent.
11 · TUTORIAL
Learn the Difference Between a Data Race and a Race Condition
These terms are often used interchangeably, but they describe different problems.
A data race involves unsynchronised conflicting access to memory where at least one access writes.
A race condition is broader. It means the correctness of the program depends on the relative timing or ordering of operations.
This matters because modern Swift can help us eliminate many forms of unsafe memory access while logical ordering problems can still remain.
Later, when you learn actors and actor reentrancy, this distinction becomes important.
You can have data-race-safe code and still make a bad assumption about what state will contain after an await.
Swift Concurrency gives us stronger guarantees.
It does not remove the need for architectural reasoning.
12 · TUTORIAL
Learn Deadlock as a Separate Problem
Deadlock is another concurrency problem, but it is not the same thing as a data race.
Deadlock occurs when execution contexts wait on one another in a cycle that cannot be resolved.
Imagine:
EXECUTION DIAGRAM
THREAD A
owns Lock A
waits for Lock B
│
▼
forever
THREAD B
owns Lock B
waits for Lock A
│
▼
forever
Thread A cannot continue until Thread B releases a resource.
Thread B cannot continue until Thread A releases a resource.
Neither can make progress.
If important application work becomes trapped in this state, the app can appear permanently frozen.
Learning concurrency therefore means learning several different classes of failure, not one vague category called “threading bugs.”
You should know what a data race is.
You should know what a race condition is.
You should know what a lost update is.
You should know what deadlock is.
Once you can describe these failures precisely, the newer Swift tools begin to look much less arbitrary.
13 · TUTORIAL
Now Ask Why Swift Concurrency Exists
Only after understanding those problems should we begin with Swift Concurrency itself.
Swift Concurrency is not simply another Apple framework sitting beside Foundation or UIKit.
It is part of the Swift language and runtime model.
That distinction matters.
The system introduces language features and runtime machinery for expressing asynchronous work, task relationships, suspension, isolation, safe transfer of values and cooperative execution.
The vocabulary includes:
CODE EXAMPLE
async
await
Task
child tasks
async let
task groups
actors
MainActor
Sendable
executors
cancellation
structured concurrency
If you begin with that list, the subject can feel like a huge collection of unrelated keywords.
If you begin with the problems, each feature has a reason to exist.
That is why the order in which you learn Swift Concurrency matters.
14 · TUTORIAL
A Task Is Not a Thread
This is one of the most important transitions from the older way of thinking.
💡 Important Terminology
A Swift Task is not a thread.
A task represents asynchronous work managed by Swift Concurrency.
During its lifetime, the task can execute synchronous work, reach a point where it may suspend, wait without needing to occupy a thread, and later continue.
A useful conceptual model is:
EXECUTION DIAGRAM
TASK
Job 1
████████
│
▼
await
│
may suspend
│
▼
waiting
│
▼
Job 2
████████
The task still exists while it is suspended.
The thread does not have to remain blocked waiting for the operation to finish.
That is one of the reasons tasks are such an important abstraction.
They allow us to describe the lifetime of asynchronous work without treating one thread as the permanent owner of that operation.
15 · TUTORIAL
Learn What await Really Means
A lot of Swift Concurrency learning goes wrong at await.
Developers often imagine that await means:
Move this work onto another thread.
It does not.
await marks a call that may suspend the current task.
Consider:
CODE EXAMPLE
@MainActor
final class Model {
var status = "Ready"
func refresh() async throws {
status = "Loading"
let launches = try await api.loadLaunches()
status = "Finished"
}
}
The code begins in a main-actor-isolated context.
When the asynchronous call is reached, the task may suspend while waiting for the result. During that suspension, the execution resource can be used for other eligible work instead of being synchronously blocked by this task.
Conceptually:
EXECUTION DIAGRAM
MAIN-ACTOR TASK
status = "Loading"
│
▼
await
│
may suspend
│
▼
other eligible work
can make progress
│
▼
status = "Finished"
The important point is that await is not a thread-switch command.
It identifies a boundary where suspension may occur.
That distinction will save you from a great deal of confusion later.
16 · TUTORIAL
Learn Cooperative Scheduling
This is where the new model becomes genuinely interesting.
Swift Concurrency uses cooperative scheduling.
A runnable synchronous portion of task work is often described as a job. Once that job is executing, it runs until it finishes that synchronous portion or reaches a point where the task can suspend.
This means Swift does not arbitrarily interrupt synchronous Swift code every few milliseconds to give every task a perfectly fair turn.
Consider:
CODE EXAMPLE
@MainActor
func calculateEverything() {
for _ in 0..<500_000_000 {
performCalculation()
}
}
Nothing about Swift Concurrency automatically makes this cheap.
If this function performs a huge amount of synchronous work on the main actor, it can still prevent main-actor work from progressing responsively.
This is why the word cooperative matters.
Your code participates in an execution model where asynchronous tasks can suspend and allow other work to proceed. But long-running synchronous work still needs to be designed responsibly.
Swift Concurrency does not make bad architecture disappear.
It gives us a better system in which to design good architecture.
17 · TUTORIAL
Understand the Swift Concurrency Runtime
The word runtime can sound more mysterious than it is.
The Swift Concurrency runtime is not another application wrapped around your app. It is runtime machinery that participates in managing Swift's task and executor system while your program is running.
A useful conceptual stack is:
EXECUTION DIAGRAM
Task
↓
Job
↓
Executor
↓
System thread
↓
Processor core
This gives us an important new layer.
With older thread-focused thinking, developers often jumped directly from “my asynchronous code” to “what thread is this on?”
Modern Swift gives us abstractions above that level.
A task describes asynchronous work.
A runnable portion of that work becomes a job.
An executor arranges for eligible jobs to execute.
That execution ultimately uses system threads and processor cores.
Once you understand this stack, a lot of Swift Concurrency starts to become much easier.
18 · TUTORIAL
Learn Executors
Executors are one of the most important concepts developers often skip.
An executor is responsible for arranging eligible Swift concurrency jobs for execution.
It is not a thread.
A task is not a thread.
An actor is not a thread.
Keeping those distinctions separate gives you a much better mental model.
EXECUTION DIAGRAM
TASK
the asynchronous operation
↓
JOB
a runnable synchronous portion
↓
EXECUTOR
arranges eligible work
↓
SYSTEM THREAD
runs machine instructions
↓
PROCESSOR CORE
executes those instructions
Once this model becomes familiar, your questions begin to change.
Instead of asking, “Which thread does this task own?”, you begin asking which isolation applies, whether the operation can suspend, which executor is responsible for eligible work, and what state can safely move across a concurrency boundary.
Those are much closer to the questions Swift Concurrency wants us to ask.
19 · TUTORIAL
Threads Can Become More of an Implementation Detail
This is one of the biggest changes in day-to-day application architecture.
With GCD-style code, we commonly wrote something like:
CODE EXAMPLE
DispatchQueue.global().async {
let result = loadData()
DispatchQueue.main.async {
self.result = result
}
}
The code explicitly talks about where work is submitted.
Modern Swift allows us to express more of the intended ownership and isolation directly.
CODE EXAMPLE
@MainActor
final class Model {
var launches: [Launch] = []
func refresh() async throws {
launches = try await api.loadLaunches()
}
}
These two examples should not be treated as mechanically equivalent. They represent different programming models.
The important change is the level at which we are thinking.
Instead of constantly designing around queues and manually moving closures between them, we increasingly design around asynchronous operations, isolation, task relationships, cancellation and safe state ownership.
The runtime coordinates much more of the lower-level execution machinery.
That is a meaningful change in architecture.
20 · TUTORIAL
Learn Actors as a Solution to Shared Mutable State
Return to our counter.
CODE EXAMPLE
counter += 1
The problem was never that incrementing an integer is inherently unsafe.
The problem was that several execution contexts could access the same mutable state without a valid synchronisation strategy.
Actors give Swift a language-level model for isolation.
CODE EXAMPLE
actor Counter {
private var value = 0
func increment() {
value += 1
}
func currentValue() -> Int {
value
}
}
Code outside the actor crosses the actor's isolation boundary when it needs actor-isolated state or behaviour.
CODE EXAMPLE
await counter.increment()
The actor protects access to its isolated mutable state so that actor-isolated operations are not simultaneously executing against that state.
This changes the architectural question.
Instead of beginning with “Which lock should protect this property?”, we can begin with:
Who owns this mutable state?
That is a far better starting point for application design.
21 · TUTORIAL
Learn MainActor
iOS developers have spent years thinking about the main thread.
Swift gives us MainActor as an isolation concept.
CODE EXAMPLE
@MainActor
final class Model {
var title = ""
}
MainActor is a global actor. Code and state isolated to it must follow the rules of that isolation domain.
On Apple platforms, main-actor execution is associated with the main-thread execution context required for UI work.
This allows us to express a requirement structurally rather than scattering manual calls such as:
CODE EXAMPLE
DispatchQueue.main.async {
// update state
}
throughout the codebase.
Again, the important shift is not simply new syntax.
The architecture now expresses ownership and isolation.
22 · TUTORIAL
Learn Structured Concurrency
Once you understand tasks, the next subject is task relationships.
Swift calls this structured concurrency.
Imagine that one operation needs to load two independent resources.
CODE EXAMPLE
async let launches = api.loadLaunches()
async let rockets = api.loadRockets()
let result = try await (launches, rockets)
The child operations can make progress concurrently.
But they also belong to the surrounding lexical scope.
Conceptually:
EXECUTION DIAGRAM
PARENT TASK
│
├── CHILD: load launches
│
└── CHILD: load rockets
This is an important improvement over asynchronous work that is started and then simply floats around the application with no obvious ownership or lifetime.
Concurrency becomes easier to reason about when work has structure.
Children belong to parents.
Errors can propagate.
Cancellation can propagate.
The lifetime of the work becomes visible in the code.
23 · TUTORIAL
Learn Cancellation
Once tasks have a lifetime, you must ask what happens when the work is no longer required.
The user leaves the screen. A newer search replaces an older search. A parent task is cancelled. One operation fails and makes other work unnecessary.
Swift gives us cancellation mechanisms such as:
CODE EXAMPLE
Task.isCancelled
and:
CODE EXAMPLE
try Task.checkCancellation()
Cancellation is not merely an optimisation.
It is part of designing asynchronous work correctly.
A mature concurrency architecture knows not only how to start work, but also when that work should stop.
24 · TUTORIAL
Learn Sendable
Concurrency is not only about where code executes.
Values also move between different isolation domains.
That means Swift needs a way to reason about whether values are safe to transfer.
This is where Sendable belongs in your curriculum.
CODE EXAMPLE
struct Launch: Sendable {
let id: UUID
let name: String
}
Do not learn Sendable as a protocol you add simply because the compiler complains.
Understand the problem it represents.
When independent concurrent parts of the program exchange values, Swift needs to be able to reason about whether that transfer preserves concurrency safety.
That is a much deeper idea than “make the warning go away.”
25 · TUTORIAL
Learn Actor Reentrancy
Actors solve an important class of shared-state problems, but they do not mean that state remains unchanged across every await.
Consider:
CODE EXAMPLE
actor LaunchStore {
var selectedID: Int?
func select(_ id: Int) async throws {
selectedID = id
let details = try await loadDetails(id)
// selectedID may have changed while this task was suspended.
save(details)
}
}
When the task reaches the await, it may suspend.
While it is suspended, another eligible job isolated to the same actor may run.
The actor still protects its isolated state from simultaneous actor-isolated access, but the logical assumptions you made before suspension may no longer be true afterward.
This is actor reentrancy.
It is a perfect example of why Swift Concurrency must be learned with understanding rather than memorisation.
The language can give you strong safety rules.
It cannot know the intended meaning of every state transition in your product.
26 · TUTORIAL
Build Your Personal Swift Concurrency Curriculum
If you genuinely want to learn Swift Concurrency, write these topics down and treat them as a study plan:
1. Concurrency vs parallelism
2. Processes
3. Threads
4. The main thread
5. Operating-system thread scheduling
6. Grand Central Dispatch
7. Shared mutable state
8. Read-modify-write operations
9. Data races
10. Race conditions
11. Interleaving
12. Deadlocks
13. Tasks
14. async
15. await
16. Suspension
17. Jobs
18. Cooperative scheduling
19. Executors
20. MainActor
21. Actors
22. Actor isolation
23. Sendable
24. Structured concurrency
25. Child tasks
26. async let
27. Task groups
28. Cancellation
29. Actor reentrancy
30. Logical races
Do not try to consume that entire list as vocabulary.
Use the sequence.
Begin at the processor and operating system.
Understand how your process contains threads.
Understand why several threads allowed concurrent work.
Understand the problems that appeared when those threads shared mutable state.
Then move into tasks, jobs, executors, suspension and isolation.
You are building one model, not memorizing thirty definitions.
27 · TUTORIAL
The Old Model and the New Model
The traditional execution model can be simplified to:
EXECUTION DIAGRAM
Process
↓
Threads
↓
Operating-system scheduler
↓
Processor cores
Swift Concurrency adds higher-level structure above those resources.
EXECUTION DIAGRAM
Task
↓
Job
↓
Executor
↓
System thread
↓
Processor core
This does not mean that threads disappeared.
It means that application developers can increasingly work at a better level of abstraction.
We can describe our software in terms of tasks, ownership, isolation and structured lifetime while allowing Swift and the operating system to coordinate much of the machinery beneath us.
That is the change worth understanding.
28 · TUTORIAL
Do Not Learn Swift Concurrency as a List of Keywords
The internet contains thousands of examples showing:
CODE EXAMPLE
func load() async {
}
and:
CODE EXAMPLE
let value = await something()
Those examples are useful once you know what they mean.
They become dangerous when they replace understanding.
Otherwise we simply replace one collection of memorised rules:
CODE EXAMPLE
Don't block the main thread.
Dispatch this to global.
Dispatch this back to main.
with another:
CODE EXAMPLE
Add async.
Put await here.
Wrap it in Task.
Add @MainActor.
That is not learning Swift Concurrency.
It is learning enough syntax to make the compiler quiet.
The goal is to understand why the syntax exists.
29 · TUTORIAL
Move Forward by Writing Cooperative Code
Swift Concurrency represents a different way of thinking about asynchronous application architecture.
We are moving away from a world where developers frequently think first in terms of creating or selecting execution queues and manually coordinating access to shared state.
We are moving toward a world where asynchronous work is represented as tasks, work can suspend instead of blocking unnecessarily, state can be isolated, task lifetimes can be structured, cancellation can propagate, and the runtime can coordinate eligible work using the execution resources underneath us.
That is what makes Swift Concurrency exciting.
It is not modern simply because it has new syntax.
It gives us new tools for solving old problems.
30 · TUTORIAL
What to Remember
💡 What to Remember
The best way to learn Swift Concurrency is to learn the execution model before trying to memorize the language features.
A process contains the running application and its execution threads.
The operating system schedules system threads.
Concurrency means several operations can make progress during overlapping periods of time. Parallelism means work is literally executing simultaneously on different processor cores.
Shared mutable state is where many traditional concurrency problems begin.
One Swift statement can involve several lower-level operations, which means another execution context can interleave work between operations you assumed belonged together.
A Swift Task is not a thread.
await marks a point where a task may suspend. It does not mean “move this code to another thread.”
Swift Concurrency uses cooperative scheduling. Long synchronous work can still block useful progress if you place it in the wrong execution context.
Actors provide isolation for mutable state.
Structured concurrency gives asynchronous work ownership and lifetime.
Sendable helps Swift reason about values crossing concurrency boundaries.
The Swift Concurrency runtime gives us a higher-level system for organising all of this work without requiring our application architecture to be designed directly around threads.
31 · TUTORIAL
Your Next Move
If you have reached this point and some of these ideas still feel uncomfortable, that is exactly where you should be.
The answer is not to read another ten definitions of async and await.
The answer is to build the model.
Draw your application's process.
Draw the main thread.
Add more threads.
Run the unsafe counter.
Watch values disappear.
Understand why the bug exists.
Then replace that older way of thinking with tasks, suspension, executors, actors and structured concurrency.
That is the journey we built 3 Days of Swift Concurrency around.
The three days are not intended to give you another collection of syntax to memorize. They are designed to move you through the execution model, the problems, the modern Swift solution and the new architectural habits one step at a time.
Start there.
Begin with the machine you already use every day.
Then begin writing software that cooperates with a whole new concurrency system instead of merely throwing more work onto another queue.
That is how you learn Swift Concurrency.
