01 · INTRODUCTION
What is a Process?
If you have been developing iOS applications for a while, you have probably used the word process hundreds of times without ever needing to stop and define exactly what it means.
We talk about killing a process, launching an app, attaching the debugger to a process, memory belonging to a process, the main thread, background threads and code executing concurrently. These phrases become part of everyday development vocabulary, and eventually it is quite easy to use them without having a clear picture of how they connect together.
That becomes a problem when learning concurrency.
To understand threads properly, you need somewhere to put them. To understand the main thread, you need to understand what it belongs to. To understand why two applications cannot simply access each other's variables, you need to understand how the operating system separates running applications. And before we can properly understand what Swift Concurrency changes, we need a basic model of what was already there.
That model begins with the process.
02 · TUTORIAL
Start With the Application You Already Know
Imagine one of the iOS applications you work on every day.
In Xcode you see Swift files, types, functions, frameworks, assets and resources. Eventually you build the project and produce an executable application.
When the user launches that application, however, the operating system needs to turn that stored program into something that is actually running.
This is where the distinction between a program and a process becomes useful.
The application stored on the device contains the executable instructions and resources required to run your software. When iOS launches that application, it creates a running execution environment for it.
We call that running instance a process.
💡 Important Terminology
A process is the operating system's representation and execution environment for a running program.
For our purposes as iOS developers, we can make the mental model even simpler.
Think of the process as a container created and managed by the operating system for your running app.
03 · TUTORIAL
Draw the Process
Whenever a computing concept feels unnecessarily abstract, draw it.
Start with a box.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ │
│ MY APP │
│ PROCESS │
│ │
│ │
│ │
└─────────────────────────────────────────┘
That box represents your running application from the operating system's point of view.
Inside it we can begin placing the things required by the application while it runs.
One of those things is its memory.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ MY APP │
│ PROCESS │
│ │
│ Application Memory │
│ │
│ objects │
│ values │
│ stacks │
│ allocated memory │
│ │
└─────────────────────────────────────────┘
The real operating-system implementation is considerably more sophisticated than this diagram, but we are deliberately building the simplest useful model first.
The process gives our running application an address space in which its memory can exist. When your Swift objects are created, values are stored, memory is allocated and your program changes state, all of that exists within the memory environment of the running process.
Already we have somewhere to put our application.
But memory alone does not execute code.
For that, we need a thread.
04 · TUTORIAL
A Process Needs a Thread to Execute Instructions
When your application starts, it has a main thread.
This gives us the next part of our diagram.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ MY APP │
│ PROCESS │
│ │
│ Application Memory │
│ │
│ Main Thread ───────────────▶ │
│ │
└─────────────────────────────────────────┘
This is one of the most useful diagrams you can commit to memory when learning concurrency.
The process is the container for the running application. The process has memory. The process contains threads.
The first thread associated with the application's main execution is what we call the main thread.
Suddenly one of the most commonly used expressions in iOS development has somewhere physical to live in our mental model.
When somebody on your team says, “That code is running on the main thread,” you should be able to picture the box.
You should see your application's process, its memory and the main thread executing instructions belonging to that process.
05 · TUTORIAL
The Thread Is the Line of Execution
A process provides the environment for the running application, but a thread provides a stream through which instructions can execute.
Think about a very simple Swift function:
CODE EXAMPLE
func launchRocket() {
prepareRocket()
startEngines()
releaseClamps()
}
We read these instructions vertically because Swift source code is written sequentially.
Conceptually, execution moves through them:
EXECUTION DIAGRAM
prepareRocket()
↓
startEngines()
↓
releaseClamps()
Eventually that Swift code is compiled into lower-level machine instructions that can be executed by a processor.
Our simplified model therefore starts to look like this:
EXECUTION DIAGRAM
MY APP'S PROCESS
Application Memory
Main Thread
────────────────────────────────────────▶
machine instructions
│
▼
PROCESSOR
This is an enormously useful connection to make.
Your Swift source code does not float around inside iOS independently. It ultimately becomes instructions that must execute using processor time.
The process contains the environment belonging to your application. Threads provide execution contexts for its instructions. The processor eventually executes those instructions.
06 · TUTORIAL
Now Imagine Several Applications Running
The real value of the process becomes clearer when we stop thinking about one application.
Imagine your iPhone has several applications running or available for execution.
For our simplified model, picture three:
EXECUTION DIAGRAM
┌───────────────────────┐
│ Photos │
│ Process │
│ │
│ Memory │
│ Main Thread ─────▶ │
└───────────────────────┘
┌───────────────────────┐
│ Messages │
│ Process │
│ │
│ Memory │
│ Main Thread ─────▶ │
└───────────────────────┘
┌───────────────────────┐
│ My App │
│ Process │
│ │
│ Memory │
│ Main Thread ─────▶ │
└───────────────────────┘
Each application has its own process.
That separation is extremely important.
The operating system is not simply throwing the instructions and memory of every running application into one giant unstructured container. Processes provide important boundaries around running programs, including their virtual address spaces and operating-system resources.
From the perspective of learning concurrency, this gives us a wonderfully simple starting point:
💡 Important Idea
One running app → one process → memory + threads.
Keep that model in your head.
07 · TUTORIAL
The Processor Processes the Process
There is an almost ridiculous sentence that is useful precisely because the words sound so similar:
The processor processes work belonging to processes.
The words processor, process and processing all come together here.
A process is not itself a processor.
The process is the operating-system environment representing the running program. Its threads provide executable streams of work, and processor cores execute the machine instructions scheduled from those threads.
A more useful diagram is therefore:
EXECUTION DIAGRAM
APP
↓
PROCESS
↓
THREAD
↓
MACHINE INSTRUCTIONS
↓
PROCESSOR CORE
If you can explain that sequence comfortably, you have already built an important part of the foundation required for understanding concurrency.
08 · TUTORIAL
Why Does Every App Need Its Own Process?
Imagine the alternative.
Suppose every application simply shared one giant memory space with every other application. Your app's objects, another application's objects, system services and every other piece of running software could potentially interfere with one another directly.
That would be an extraordinarily difficult system to make safe and reliable.
Processes provide isolation between running programs. Each process has its own virtual address space and operating-system-managed resources, and the kernel controls interactions across those boundaries.
This is why the idea of a process is much bigger than merely saying “an app that is currently open.”
It is part of the operating system's architecture for managing, isolating and executing programs.
As an iOS developer, you do not normally create the application process yourself. iOS manages that lifecycle for you.
But your code lives inside it every time your application runs.
09 · TUTORIAL
The Process Contains More Than One Thread
Now we arrive at the part that matters enormously for concurrency.
Our application begins with its main thread, but a process can contain multiple threads.
So our diagram can grow:
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ MY APP │
│ PROCESS │
│ │
│ Application Memory │
│ │
│ Main Thread ───────────────▶ │
│ Thread 2 ───────────────▶ │
│ Thread 3 ───────────────▶ │
│ │
└─────────────────────────────────────────┘
Now something important has changed.
Our application no longer has only one stream of execution.
It has several.
These threads belong to the same process and therefore operate within the same process address space. That ability is extremely powerful because different pieces of work can make progress concurrently.
It is also where some of our biggest problems begin.
10 · TUTORIAL
Why Did We Need More Than One Thread?
Imagine that our application had only its main thread.
Perhaps the user taps a button and we begin an expensive synchronous operation:
CODE EXAMPLE
func buttonPressed() {
calculateSomethingEnormous()
}
If that work occupies the main thread for several seconds, other work requiring main-thread execution cannot simply leap over it.
The instructions already executing have to make progress.
For a UI application, the consequence is obvious.
The interface stops responding properly. Animations stop progressing smoothly. Touches appear to do nothing. The application feels frozen.
One historical solution was to allow other work to execute using additional threads rather than forcing every operation through one execution stream.
That is why understanding the process naturally leads us into understanding threads.
11 · TUTORIAL
Processes Give Threads Shared Access to the App's Memory
There is another extremely important consequence of having multiple threads inside one process.
They operate within the same process address space.
That means different threads may reach the same mutable application state.
Imagine:
CODE EXAMPLE
var counter = 0
Now imagine two threads executing code that modifies it.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ PROCESS │
│ │
│ counter = 0 │
│ ▲ ▲ │
│ │ │ │
│ │ │ │
│ Thread 1 Thread 2 │
│ │
└─────────────────────────────────────────┘
This ability is useful.
It is also dangerous.
If both threads read and modify the value without appropriate synchronisation, the ordering of their operations can produce unexpected results.
This is where understanding a process becomes directly relevant to understanding race conditions.
The threads are not executing inside completely independent copies of your application.
They are execution contexts operating within the same process.
They can therefore interact with shared application state.
12 · TUTORIAL
This Is Where Concurrency Problems Begin to Make Sense
Suppose two threads both execute:
CODE EXAMPLE
counter += 1
At the Swift level, that looks like one operation.
But conceptually the underlying work involves reading the existing value, calculating a new value and writing the result.
Now imagine:
EXECUTION DIAGRAM
THREAD 1 THREAD 2
READ counter → 10
READ counter → 10
ADD 1 → 11
ADD 1 → 11
WRITE 11
WRITE 11
Two increments were requested.
The final value is 11, not 12.
Once you understand that both threads exist inside the same process and can reach shared process memory, this bug stops looking mysterious.
The architecture explains the problem.
This is precisely why we should learn concurrency from the operating system upward instead of beginning with async and await.
13 · TUTORIAL
Grand Central Dispatch Did Not Remove Processes or Threads
When Apple introduced Grand Central Dispatch, the underlying process-and-thread model did not disappear.
GCD gave developers a higher-level way of submitting work.
Instead of manually managing a thread, we could write:
CODE EXAMPLE
DispatchQueue.global().async {
performWork()
}
The important point is that the global queue is not itself “the background thread.”
We submit work to a queue, and GCD coordinates execution using underlying system resources.
Likewise:
CODE EXAMPLE
DispatchQueue.main.async {
updateInterface()
}
submits work to the main dispatch queue, whose execution is associated with the application's main thread.
The abstraction became better.
The underlying operating-system concepts remained important.
That same pattern is worth remembering as we move into Swift Concurrency.
14 · TUTORIAL
Swift Concurrency Does Not Replace the Process
Swift Concurrency does not mean your application suddenly runs inside some completely different operating-system architecture.
Your iOS application still runs as a process.
System threads still exist.
Processor cores still execute machine instructions.
The operating system still schedules execution resources.
Swift Concurrency adds a much richer programming and runtime model above those lower-level mechanisms.
This is an important distinction.
We are not replacing:
EXECUTION DIAGRAM
PROCESS
↓
THREAD
↓
PROCESSOR
with something that makes those concepts cease to exist.
We are adding higher-level concepts that allow us to structure concurrent software differently.
A useful simplified progression is:
EXECUTION DIAGRAM
Swift Task
↓
Job
↓
Executor
↓
System Thread
↓
Processor Core
All of that work is still happening as part of your running application process.
15 · TUTORIAL
This Is Why a Task Is Not a Process
Once you begin learning Swift Concurrency, there are suddenly several words that sound like containers for work.
A process is an operating-system concept representing a running program and its execution environment.
A thread is a system execution context within a process.
A Swift Task represents asynchronous work managed by Swift Concurrency.
These are different layers.
EXECUTION DIAGRAM
OPERATING SYSTEM
│
└── APP PROCESS
│
├── Memory
│
├── System Threads
│
└── Swift Concurrency Runtime
│
├── Tasks
├── Jobs
└── Executors
The diagram is simplified, but the separation is valuable.
A task does not replace your application's process.
A task does not own an entire application.
A task is not another mini-process created every time you write:
CODE EXAMPLE
Task {
await loadData()
}
It is an asynchronous unit of work participating in Swift's concurrency system.
16 · TUTORIAL
Why This Matters When Learning Swift Concurrency
It is tempting to skip processes because they feel like an operating-systems topic rather than an iOS development topic.
I think that is a mistake.
If you do not understand the process, the thread becomes an abstract word.
If the thread is abstract, the main thread becomes a memorised rule.
If the main thread is only a memorised rule, dispatch queues become magical places where code somehow “goes.”
Then Swift Concurrency arrives and adds tasks, actors, executors, suspension and isolation on top of an execution model that was never properly understood in the first place.
Of course it feels confusing.
The solution is not another diagram showing where to type await.
The solution is to fill in the missing layer.
17 · TUTORIAL
Draw Your Own App's Process
This is the exercise I would actually recommend doing.
Take the application you work on professionally, or one of your own side projects, and draw a large rectangle.
Write:
CODE EXAMPLE
MY APP'S PROCESS
at the top.
Add application memory.
Then add:
CODE EXAMPLE
Main Thread
Now add two more threads.
Then draw the processor outside the process.
Your diagram should look approximately like this:
EXECUTION DIAGRAM
┌─────────────────────────────────────────────┐
│ MY APP'S PROCESS │
│ │
│ APPLICATION MEMORY │
│ │
│ Main Thread ───────────────────────▶ │
│ Thread 2 ───────────────────────▶ │
│ Thread 3 ───────────────────────▶ │
│ │
└─────────────────────────────────────────────┘
│
│ scheduled execution
▼
┌──────────────┐
│ PROCESSOR │
│ CORES │
└──────────────┘
Do not worry yet about every detail that is missing from this diagram.
The point is to establish the architecture.
Once you can draw this without looking it up, start asking questions.
Where does the main thread belong?
Inside the process.
Where does your application's memory belong?
To the process's address space.
Can a process contain several threads?
Yes.
Can those threads access shared application state?
Yes, subject to the program's memory and synchronization rules.
Can several threads make progress concurrently?
Yes.
Can that create correctness problems?
Absolutely.
Now you are ready to learn why modern concurrency systems exist.
18 · TUTORIAL
What to Remember
💡 What to Remember
The operating system runs your iOS application as a process.
For learning purposes, think of that process as the operating-system-managed environment containing your running application, its address space and its threads.
Your application begins with a main thread, and the process can contain additional threads.
Threads provide execution contexts through which your compiled instructions ultimately run on processor cores.
Because threads within the same process operate in the same process address space, they can interact with shared mutable application state. That capability makes concurrent software powerful, but it also creates problems such as data races, race conditions and lost updates when state is not correctly protected.
Grand Central Dispatch gave us a higher-level abstraction for submitting work without requiring application developers to manually manage every thread.
Swift Concurrency takes us further again.
But neither GCD nor Swift Concurrency makes the underlying process disappear.
Your application still runs as a process. Threads still exist. Processor cores still execute machine instructions.
Swift Concurrency gives us a new system for expressing and coordinating asynchronous work above those foundations.
19 · TUTORIAL
Your Next Move
Do not finish this article by memorising the sentence “a process is a running program.”
Draw one.
Put your own iOS application inside it.
Add its memory.
Add the main thread.
Add more threads.
Connect those execution contexts conceptually to the processor.
Then ask what happens when two of those threads reach the same mutable value.
That question takes you directly into the next part of the curriculum: threads, shared state and the concurrency problems that Swift Concurrency was designed to help us solve.
This is how we approach the subject throughout 3 Days of Swift Concurrency. We start underneath the syntax, build the execution model, deliberately encounter the problems, and then introduce the modern Swift solution when there is a reason for it to exist.
Because once you understand what a process is, the main thread finally has somewhere to live.
And once you understand the main thread, you are one step closer to understanding concurrency.
