top of page

01 · INTRODUCTION

Does My App Process Provide a Default Thread?

Yes. When your iOS application starts, its process begins execution with one initial thread. We call this the main thread.

This is the thread on which your application's initial execution begins, and it becomes particularly important on iOS because the application's main event-processing and user-interface work is associated with it.

Before you create a Task, before you dispatch anything to a global queue, and before your own code deliberately introduces additional concurrent work, there is already an execution context through which the application can begin running.

That execution context is the main thread.

💡 The Answer

An iOS application begins execution with an initial thread: the main thread.

Additional threads may later exist inside the same process, but the main thread is the thread from which the application's initial execution begins.

That sounds simple, but understanding where this thread comes from and why it became so important will connect several pieces of iOS development that developers often learn as completely separate rules.

02 · TUTORIAL

Start With the Process

In the previous articles we established that iOS does not simply throw your application's machine code directly at the processor.

Your running application exists inside an operating-system process.

A simplified picture looks like this:

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              MY APP PROCESS             │
│                                         │
│          Application Memory             │
│                                         │
│              Main Thread                │
│                                         │
└─────────────────────────────────────────┘

This diagram is worth remembering because it gives the phrase main thread somewhere to live.

The main thread is not a global thread shared between every application running on the device.

It belongs to your application's process.

Another application has another process and its own main thread.

EXECUTION DIAGRAM

┌─────────────────────────┐
│     YOUR APP PROCESS    │
│                         │
│       Main Thread       │
└─────────────────────────┘


┌─────────────────────────┐
│   ANOTHER APP PROCESS   │
│                         │
│       Main Thread       │
└─────────────────────────┘

This is one of the simplest ways to stop thinking of “the main thread” as some mysterious global iOS resource.

It is the main thread of your process.

03 · TUTORIAL

Why Must a Process Have a Thread?

Because a process by itself is not enough to execute instructions.

The process provides the environment in which the application runs. It gives the application its virtual address space and operating-system resources.

But something still needs an execution context through which the application's machine instructions can actually run.

That is the role of a thread.

Think of the distinction like this:

EXECUTION DIAGRAM

PROCESS

Provides the running
application environment

        │
        ▼

THREAD

Provides an execution
context within that process

        │
        ▼

CPU CORE

Executes machine
instructions

So creating a process with no way to begin executing its program would not get us very far.

The application needs an initial execution context.

That initial thread becomes the main thread.

04 · TUTORIAL

The Main Thread Is Thread Number One in Our Mental Model

Throughout this series we have deliberately drawn our threads like this:

CODE EXAMPLE

MY APP PROCESS

Main Thread
Thread 2
Thread 3
Thread 4

There is a reason we do not draw:

CODE EXAMPLE

Thread 1
Thread 2
Thread 3
Thread 4

and leave them all conceptually identical.

For our learning model, Thread 1 is the main thread.

It is the initial thread through which the application's execution begins.

Additional threads may later be created or managed within the process:

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              MY APP PROCESS             │
│                                         │
│    Thread 1 ── Main Thread              │
│    Thread 2                             │
│    Thread 3                             │
│    Thread 4                             │
│                                         │
└─────────────────────────────────────────┘

This is a very useful diagram to be able to draw from memory.

05 · TUTORIAL

Where Does main() Fit Into This?

Most Swift iOS developers rarely write or even see a traditional main() function.

That can make the application's true entry point feel invisible.

In a simple native program, we can imagine execution beginning at an entry point such as:

CODE EXAMPLE

func main() {
    // begin program
}

Modern Swift iOS applications hide much of this startup machinery behind language and framework features.

For example, a SwiftUI application commonly begins with something resembling:

CODE EXAMPLE

@main
struct RocketLaunchApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

The @main attribute identifies the type that provides the program's entry point.

You are not manually creating the initial thread here.

The application startup machinery has already established the environment required for your program to begin execution.

So conceptually we can draw:

EXECUTION DIAGRAM

USER LAUNCHES APP
        │
        ▼
iOS starts application process
        │
        ▼
Initial execution begins
on the main thread
        │
        ▼
Application entry point
        │
        ▼
Framework startup
        │
        ▼
Your application lifecycle

This is the larger picture behind the few lines of Swift we normally see.

06 · TUTORIAL

UIKit Hides the Entry Point Too

UIKit developers may be even more familiar with the feeling that their application somehow “just starts.”

Historically, an iOS application's entry point ultimately leads into UIKit's application startup machinery, including UIApplicationMain.

In Swift projects, attributes and generated startup code mean developers normally do not need to manually write this plumbing.

Instead, we encounter lifecycle callbacks such as:

CODE EXAMPLE

func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions:
        [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

    return true
}

By the time our application delegate receives this callback, a substantial amount of startup work has already happened.

The important point for our concurrency model is that our program did not begin without an execution context and then somehow discover the main thread later.

The main thread is there from the beginning of application execution.

07 · TUTORIAL

Why Did the Main Thread Become the UI Thread?

Now we reach the part every iOS developer recognises.

We are repeatedly told:

CODE EXAMPLE

Update the UI on the main thread.

Why?

It is tempting to think that there is something physically special about the first thread that makes it capable of drawing pixels while other threads cannot.

That is not the useful explanation.

The significance comes from the architecture of the application frameworks and event-processing model built around the application's main thread.

UIKit and AppKit have historically required user-interface interactions to occur from the application's main thread. Main run-loop processing, user events and UI updates are therefore deeply associated with this execution context.

The rule exists so that the enormous amount of mutable state involved in a graphical interface does not need to behave as though every UI object can be freely mutated concurrently from arbitrary threads.

Instead, there is a privileged execution context for UI work.

EXECUTION DIAGRAM

MAIN THREAD

User Events
     │
     ▼
Application Logic
     │
     ▼
UI State Changes
     │
     ▼
Layout / Display Work
     │
     ▼
Next Interaction

This gives the application an ordered place from which its user-interface work can be coordinated.

08 · TUTORIAL

The Main Thread Has a Run Loop

Another term that often appears in iOS development is the main run loop.

This is closely related to why the main thread can remain alive for the entire lifetime of an application rather than simply reaching the end of its startup function and disappearing.

A graphical application needs to continue responding to events.

The user might tap a button.

A timer may fire.

An input source may become ready.

The system may need to deliver another event.

The application therefore participates in an event-processing loop.

A deliberately simplified mental model is:

EXECUTION DIAGRAM

             MAIN RUN LOOP

                  │
                  ▼
            Wait for event
                  │
                  ▼
            Receive event
                  │
                  ▼
            Handle event
                  │
                  ▼
        Perform required work
                  │
                  ▼
        Update application / UI
                  │
                  ▼
            Wait for event
                  │
                  └──────────────┐
                                 │
                  ◀──────────────┘

The real system is considerably more sophisticated, but this gives us the correct architectural intuition.

Your application is not executing one gigantic function from launch until termination.

It spends much of its lifetime responding to work and events as they become available.

09 · TUTORIAL

This Explains the Phrase “Do Not Block the Main Thread”

We can now finally explain one of the most repeated sentences in iOS development without treating it as a rule to memorise.

Imagine the main thread is handling normal application work:

EXECUTION DIAGRAM

MAIN THREAD

Handle touch
    ↓
Update state
    ↓
Perform UI work
    ↓
Return to event processing

Now imagine we insert an enormous synchronous calculation:

CODE EXAMPLE

func calculateEverything() {

    for _ in 0..<500_000_000 {
        performCalculation()
    }
}

If that work occupies the main thread for a significant amount of time, the thread cannot simultaneously execute the other work waiting for that same execution context.

EXECUTION DIAGRAM

MAIN THREAD

Handle touch
    ↓
START HUGE CALCULATION
████████████████████████████████
████████████████████████████████
████████████████████████████████
    ↓
Calculation finally finishes
    ↓
Other main-thread work continues

During that interval, the application can stop responding smoothly to the user.

The UI may appear frozen or jerky because the execution context responsible for important UI and event-processing work is occupied doing something else.

Now “do not block the main thread” has a physical meaning.

10 · TUTORIAL

A 60 FPS Interface Has Very Little Time

Many iOS interfaces aim to update at 60 frames per second, while modern displays can operate at higher refresh rates.

At 60 frames per second, one frame occupies approximately:

CODE EXAMPLE

1 second ÷ 60

≈ 16.67 milliseconds per frame

That is not much time.

If synchronous main-thread work prevents the system from completing the necessary work for upcoming frames in time, frames can be missed.

The user experiences that as hitching, stuttering or an interface that temporarily freezes.

This is why main-thread performance is not merely an abstract concurrency concern.

Users can feel it with their fingers.

11 · TUTORIAL

Does That Mean All My Code Starts on the Main Thread?

Be careful with this statement.

Your application's initial execution begins on its main thread, but a modern iOS application quickly becomes a much more complicated system.

Frameworks may perform work using other threads.

GCD may execute submitted work using worker threads.

Swift Concurrency manages asynchronous work using its runtime and executors.

Networking, media, database and system frameworks may have their own internal concurrency implementations.

So this:

CODE EXAMPLE

My app has a main thread.

is correct.

But this:

CODE EXAMPLE

Everything in my application
therefore executes on the main thread.

is not.

Once concurrency enters the application, many execution contexts can be involved.

12 · TUTORIAL

The Main Thread Is Not the Main Queue

This distinction is extremely important.

iOS developers often use the phrases main thread and main queue almost interchangeably because work submitted to the main dispatch queue executes on the application's main thread.

But they describe different things.

The main thread is an operating-system thread.

The main queue is a Grand Central Dispatch queue.

Think of the relationship as:

EXECUTION DIAGRAM

DispatchQueue.main
       │
       │ submits / organises work
       ▼
MAIN DISPATCH QUEUE
       │
       │ executed on
       ▼
MAIN THREAD
       │
       ▼
CPU CORE

This is an excellent example of why learning the layers matters.

A queue is not a thread.

A thread is not a queue.

The main dispatch queue is associated with execution on the main thread.

13 · TUTORIAL

This Is What We Were Doing With GCD

Before Swift Concurrency, an extremely common iOS pattern looked like this:

CODE EXAMPLE

DispatchQueue.global().async {

    let result = performExpensiveWork()

    DispatchQueue.main.async {
        updateUI(with: result)
    }
}

Read this now using the architecture we have learned.

First we submit expensive work to a global dispatch queue.

EXECUTION DIAGRAM

WORK
  │
  ▼
GLOBAL DISPATCH QUEUE
  │
  ▼
GCD-managed worker execution

When that work finishes, we submit the UI update to:

EXECUTION DIAGRAM

DispatchQueue.main
       │
       ▼
MAIN QUEUE
       │
       ▼
MAIN THREAD

This pattern makes much more sense once queues and threads stop being treated as the same concept.

14 · TUTORIAL

Now Enter @MainActor

Swift Concurrency gives us a different way to express an important part of this requirement.

Instead of scattering:

CODE EXAMPLE

DispatchQueue.main.async {
    // mutate UI state
}

throughout our application, Swift Concurrency gives us the MainActor.

For example:

CODE EXAMPLE

@MainActor
final class LaunchModel {

    var launches: [Launch] = []

    func refresh() async throws {
        launches = try await api.loadLaunches()
    }
}

The important architectural shift is that we are no longer merely saying:

CODE EXAMPLE

Put this closure onto the main queue.

We are expressing an isolation requirement.

CODE EXAMPLE

This state belongs to the MainActor.

That is a much stronger and more useful statement about our program's architecture.

15 · TUTORIAL

Is the MainActor Just Another Name for the Main Thread?

No.

They are closely related in normal iOS application development, but they belong to different layers of the system.

The main thread is an operating-system execution resource.

The MainActor is a Swift concurrency isolation mechanism represented by a global actor.

Its executor is associated with the application's main-thread execution environment, which is why MainActor-isolated work is the modern Swift mechanism we use for state that must be coordinated with main-thread-bound application and UI work.

A useful simplified diagram is:

EXECUTION DIAGRAM

SWIFT CONCURRENCY

@MainActor
     │
     ▼
MainActor isolation
     │
     ▼
MainActor executor
     │
     ▼
Main-thread execution
     │
     ▼
OS scheduling
     │
     ▼
CPU core

This distinction prevents a very common learning mistake.

@MainActor is not merely modern spelling for:

CODE EXAMPLE

DispatchQueue.main.async

It expresses isolation as part of the program's concurrency model.

16 · TUTORIAL

Why Isolation Is Better Than Remembering to Dispatch

Imagine a model whose mutable state is used by the UI.

With a purely manual approach, every developer touching that model has to remember the rule:

CODE EXAMPLE

Whenever you mutate this state,
remember to get onto the main queue.

That is knowledge stored in people's heads.

And people forget.

With actor isolation, we can express the requirement in the program itself:

CODE EXAMPLE

@MainActor
final class ProfileModel {

    var username = ""
    var image: UIImage?

}

Now the isolation rule is attached to the type.

That is a profound difference.

We are moving from:

CODE EXAMPLE

Developers must remember
where this code should execute.

toward:

CODE EXAMPLE

The program describes
where this state is isolated.

This is one of the reasons Swift Concurrency is much more than a convenient replacement for completion handlers.

17 · TUTORIAL

The Main Thread Still Exists

It is important not to overcorrect when learning Swift Concurrency.

Sometimes developers hear:

CODE EXAMPLE

Don't think about threads anymore.

and interpret that as:

CODE EXAMPLE

Threads no longer matter.

That is not what happened.

The main thread still exists.

System threads still exist.

The operating system still schedules threads onto processor cores.

Processor cores still execute machine instructions.

Swift Concurrency gives us better abstractions above those mechanisms so that our application's architecture does not need to be built around manually managing them.

Understanding the lower layer therefore remains useful.

We simply do not want to program every feature directly at that layer.

18 · TUTORIAL

Follow the Main Thread From Launch to the Processor

We can now draw one of the most complete diagrams in this series.

EXECUTION DIAGRAM

USER TAPS APP ICON
        │
        ▼
iOS launches application
        │
        ▼
┌───────────────────────────────┐
│        APP PROCESS            │
│                               │
│        Main Thread            │
│             │                 │
│             ▼                 │
│     Application Entry        │
│             │                 │
│             ▼                 │
│     Framework Lifecycle      │
│             │                 │
│             ▼                 │
│     Main Event Processing    │
│                               │
└──────────────┬────────────────┘
               │
               ▼
         OS SCHEDULER
               │
               ▼
           CPU CORE
               │
               ▼
     MACHINE INSTRUCTIONS

That is what the phrase main thread is connected to.

It is not merely a warning printed by Xcode when we update a label incorrectly.

It is part of the execution architecture of the running application.

19 · TUTORIAL

Now Add Additional Threads

As the application becomes concurrent, the process can contain additional threads:

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              MY APP PROCESS             │
│                                         │
│       Main Thread                       │
│       Thread 2                          │
│       Thread 3                          │
│       Thread 4                          │
│                                         │
│       Shared Process Memory             │
│                                         │
└─────────────────────────────────────────┘
                 │
                 ▼
           OS SCHEDULING
                 │
                 ▼
             CPU CORES

Now several execution contexts belonging to the same application can make progress.

And because those threads operate within the same process and can access shared process memory, we immediately rediscover the concurrency problems discussed earlier in this series.

Race conditions.

Interleaving.

Lost updates.

Locks.

Deadlocks.

Thread-safety requirements.

This is precisely the historical problem space from which modern Swift Concurrency is helping us move toward structured tasks and isolated state.

20 · TUTORIAL

Why This Matters to an iOS Developer

Many developers learn the main thread as a collection of rules.

CODE EXAMPLE

UI on main thread.

Networking off main thread.

Don't block main thread.

Dispatch back to main.

Use @MainActor.

You can memorise all five statements and still have very little understanding of what the main thread actually is.

Our goal is different.

We want to be able to explain the system.

Your application runs inside a process.

Execution begins with an initial thread: the main thread.

That thread becomes deeply associated with the application's event-processing and user-interface environment.

If long-running synchronous work occupies that execution context, other important main-thread work cannot make progress in time and the user experiences an unresponsive or jerky interface.

GCD gave us the main dispatch queue as a mechanism for scheduling work onto that execution context.

Swift Concurrency gives us the MainActor as a way of expressing isolation for state and work that belongs to the main-actor domain.

Now those five rules are no longer disconnected facts.

They are one architecture.

21 · TUTORIAL

What to Remember

💡 What to Remember

Yes. An iOS application's execution begins with an initial thread, which we call the main thread.

The main thread belongs to your application's process. Other applications have their own processes and their own main threads.

The process provides the running environment and virtual address space for the application. A thread provides an execution context through which instructions belonging to that process can run.

The main thread is important on iOS because the application's primary event-processing and user-interface environment is associated with it.

The main run loop allows the application to continue receiving and processing events throughout its lifetime.

Long-running synchronous work on the main thread can prevent important UI and event-processing work from making timely progress, which is why applications can freeze or become jerky when the main thread is blocked.

The main thread and the main dispatch queue are not the same thing. The main thread is an operating-system thread; DispatchQueue.main is a GCD queue whose work executes on the main thread.

The MainActor is also not simply another name for the main thread. It is Swift's global-actor isolation mechanism for work and state associated with the main execution domain.

Swift Concurrency does not remove the main thread. It gives us a safer, higher-level architecture for describing which state and work belongs there.

22 · TUTORIAL

Your Next Move

We now know where our application's first thread comes from.

We know that it lives inside our application's process.

We know why it became the main thread.

And we know why occupying it for too long can make an application appear frozen.

But look again at our diagram:

EXECUTION DIAGRAM

┌───────────────────────────────┐
│         APP PROCESS           │
│                               │
│         Main Thread           │
│         Thread 2              │
│         Thread 3              │
│                               │
└───────────────┬───────────────┘
                │
                ▼
             CPU CORE

There is still a subtle question hiding inside it.

What actually gets connected to the processor?

Does the operating system give the CPU an entire process?

Does the CPU somehow enter our application's process and begin looking for work?

Or is it specifically one thread's execution context that is scheduled onto a processor core while the process provides the memory environment around it?

Getting this distinction right will complete one of the most important diagrams in our entire concurrency model.

That is the next article:

What Plugs Into the Processor: a Process or the Thread?

bottom of page