top of page

01 · INTRODUCTION

Swift Concurrency Runtime

The short answer

The Swift Concurrency runtime is the scheduling system that manages Swift tasks while an application is running.

It keeps track of work that can execute now, work that is suspended, and work that has become ready to continue. It then cooperates with executors and the operating system so that eligible pieces of asynchronous code can make progress.

This is the machinery that gives meaning to Task, async, await, actors and structured concurrency.

EXECUTION DIAGRAM

SWIFT CONCURRENCY

Our async code
      │
      ▼
Tasks containing asynchronous work
      │
      ▼
Swift Concurrency runtime
      │
      ├── tracks runnable work
      ├── records suspended work
      ├── responds when waiting completes
      └── submits eligible jobs to executors
              │
              ▼
         System threads
              │
              ▼
         Processor cores

This is a conceptual model. The runtime is not one public Swift object and its complete implementation is more sophisticated than this diagram.

💡 Important Terminology

Task = a unit of asynchronous work managed by Swift.

Job = a runnable portion of a task.

Executor = a service that accepts jobs and arranges for them to execute.

Thread = an operating-system execution context on which machine instructions run.

02 · TUTORIAL

From main() to an Event-Driven Application

Begin With a Normal Swift Program

Before thinking about concurrency, imagine the smallest possible Swift program.

CODE EXAMPLE

func main() {
    print("Application started")
    loadConfiguration()
    createInitialModel()
}

main()

Execution enters main(), calls each function in order and eventually reaches the end.

EXECUTION DIAGRAM

ENTER main()
     │
     ▼
print("Application started")
     │
     ▼
loadConfiguration()
     │
     ▼
createInitialModel()
     │
     ▼
RETURN FROM main()

This is a synchronous mental model.

The current function owns the flow of execution. It calls another function, waits for that function to return and then continues with its next instruction.

If these were the only instructions in a command-line program, returning from main() would allow the program to end.

An iOS Application Must Remain Alive

An iOS application cannot finish after creating its first model and interface.

It must remain alive to receive touches, timers, lifecycle changes, network completions and requests to update the interface.

In a modern SwiftUI application, the visible entry point is normally a type marked with @main:

CODE EXAMPLE

@main
struct RocketLaunchApp: App {
    var body: some Scene {
        WindowGroup {
            LaunchListView()
        }
    }
}

The compiler and Apple frameworks provide the lower-level entry machinery. They establish the application lifecycle and keep the process alive through an event-driven system organised around the main thread and its run loop.

A deliberately simplified version might look like this:

CODE EXAMPLE

func main() {
    let application = createApplication()
    let firstScene = RocketLaunchApp()

    application.install(firstScene)
    application.run()
}

application.run() does not simply call our view once and return. It enters the framework-controlled lifetime of the app.

EXECUTION DIAGRAM

APPLICATION LIFETIME — CONCEPTUAL MODEL

Start application
      │
      ▼
Create initial scene
      │
      ▼
Wait for ready events
      │
      ▼
Execute event handlers
      │
      ▼
Allow UI update work to progress
      │
      └──────────────► Wait again

The run loop helps the main thread wait efficiently and process sources of main-thread work when they become ready.

This existed before Swift Concurrency.

03 · TUTORIAL

Now Imagine Installing a Task Manager

We can now introduce a useful teaching model.

Imagine that application startup also created one long-lived task manager.

The following code is conceptual pseudocode. TaskManager is not a real Swift runtime API:

CODE EXAMPLE

func main() {
    let application = createApplication()
    let taskManager = TaskManager()

    taskManager.connect(to: application)

    application.install(RocketLaunchApp())
    application.run()
}

The task manager would not contain one fixed sequence of application instructions.

It would manage a changing collection of tasks.

CODE EXAMPLE

final class TaskManager {
    private var runnableTasks: [ManagedTask] = []
    private var suspendedTasks: [ManagedTask] = []

    func submit(_ task: ManagedTask) {
        runnableTasks.append(task)
        scheduleEligibleWork()
    }

    func taskDidSuspend(_ task: ManagedTask) {
        moveToSuspendedTasks(task)
    }

    func waitingDidFinish(for task: ManagedTask) {
        moveToRunnableTasks(task)
        scheduleEligibleWork()
    }
}

Swift does not expose its concurrency runtime as a class named TaskManager, and an application does not create it manually.

However, the responsibilities in this example reveal the important change:

• new asynchronous work can be submitted;

• work that can execute is treated differently from work that is waiting;

• a suspended task does not need to occupy a thread;

• and work can become eligible to continue after an event completes.

💡 The Useful Mental Model

Imagine the Swift Concurrency runtime as a system-wide task manager installed as part of the running program.

Our code creates tasks. The runtime tracks when their next work is eligible to execute.

04 · TUTORIAL

Does the Runtime Execute Once Per Run-Loop Cycle?

Not literally.

It is useful to imagine the task manager being connected to the application's event-driven lifetime, but the Swift Concurrency runtime is not simply a function called once at the end of every run-loop cycle.

The run loop and the concurrency runtime solve related but different scheduling problems.

CODE EXAMPLE

MAIN RUN LOOP

Coordinates sources of work associated with the main thread


SWIFT CONCURRENCY RUNTIME

Tracks asynchronous tasks and makes eligible task jobs available to executors


OPERATING SYSTEM

Schedules runnable threads onto processor cores

Main-actor jobs must ultimately be arranged on the application's main execution domain. This is where the systems meet.

Other eligible task jobs can be offered to an appropriate executor and executed using system-managed threads without waiting for the next main run-loop cycle.

So the accurate version of our analogy is:

💡 Reality Check

The runtime is connected to the lifetime and scheduling machinery of the process, not implemented as one task-manager callback that the main run loop fires once per loop.

05 · TUTORIAL

How the Runtime Manages a Task

Creating a Task Submits Work to the Runtime

Consider a button that starts loading rocket launches:

CODE EXAMPLE

Button("Load Launches") {
    Task {
        await loadLaunches()
    }
}

Task {} creates a task and gives Swift a closure containing its initial work.

The important architectural change is that the button handler does not synchronously execute the complete lifetime of loadLaunches() before returning.

It creates a managed unit of asynchronous work.

EXECUTION DIAGRAM

BUTTON HANDLER
      │
      ▼
Create Task
      │
      ▼
Runtime records eligible task work
      │
      ▼
Executor arranges execution
      │
      ▼
Task begins or resumes on a system thread

A task is not a thread.

The task is the logical lifetime of the asynchronous operation. Threads are execution resources used underneath when portions of that task are ready to run.

A Task Can Stop Without Blocking Its Thread

This is the feature that makes the runtime more than a queue of closures.

CODE EXAMPLE

func loadLaunches() async throws -> [Launch] {
    let (data, _) = try await URLSession.shared.data(
        from: launchEndpoint
    )

    return try JSONDecoder().decode([Launch].self, from: data)
}

The task begins executing the function and reaches the call to URLSession.

If the awaited operation cannot complete immediately, the current task can suspend.

EXECUTION DIAGRAM

TASK: LOAD LAUNCHES

Running
  │
  ▼
Begin URL request
  │
  ▼
Possible suspension point
  │
  ├──────────── Suspended ────────────┐
  │                                   │
  │    No thread is blocked merely   │
  │    to preserve this wait          │
  │                                   │
  └──── Network operation completes ──┘
  │
  ▼
Runnable again
  │
  ▼
Decode response

The runtime retains the information required for the task to continue later.

The thread that had been executing the task is not required to sit idle until the server responds. It can be used to execute other eligible work.

When the awaited operation completes, the task becomes eligible to continue. An executor can then arrange for its next job to run.

await marks a possible suspension point. It does not guarantee suspension, promise a background thread or directly move code to another thread.

A Task Is Executed in Pieces

Synchronous code encourages us to imagine one function owning one uninterrupted path from its first instruction to its return value.

An asynchronous task has a longer logical lifetime.

CODE EXAMPLE

func refreshLaunches() async throws {
    showLoadingState()

    let launches = try await api.loadLaunches()

    sort(launches)

    await store(launches)
}

The task may execute one synchronous portion, suspend, and later execute another portion.

EXECUTION DIAGRAM

ONE LOGICAL TASK

Job 1
showLoadingState()
begin loadLaunches()
        │
        ▼
     SUSPEND
        │
        ▼
Job 2
receive launches
sort launches
begin store(...)
        │
        ▼
 POSSIBLE SUSPENSION
        │
        ▼
Job 3
finish task

The task is the complete asynchronous operation.

The jobs are the runnable portions between suspension points.

This distinction explains how thousands of tasks can exist without requiring thousands of threads. Most tasks may be suspended while only a smaller set of runnable jobs needs execution resources.

06 · TUTORIAL

Executors Decide Where Eligible Jobs Belong

The runtime does not treat every job as interchangeable.

Actor isolation can require work to execute through a particular executor.

CODE EXAMPLE

@MainActor
final class LaunchViewModel: ObservableObject {
    @Published private(set) var launches: [Launch] = []
    @Published private(set) var isLoading = false

    func refresh() async {
        isLoading = true
        defer { isLoading = false }

        do {
            launches = try await LaunchAPI().loadLaunches()
        } catch {
            launches = []
        }
    }
}

The view model is isolated to MainActor.

Its isolated state must be accessed through the main actor's executor. This gives the program an ordered isolation domain for these state changes.

When refresh() suspends while awaiting the API, the main actor is free to run other eligible jobs. The actor is not owned by the suspended task.

When the API result is ready, the continuation that needs main-actor isolation becomes eligible for the main actor's executor.

EXECUTION DIAGRAM

LOAD TASK                         MAIN ACTOR

Start refresh()             ───► isLoading = true
       │
       ▼
Await network request
       │
    suspended               ───► other eligible UI work
       │
       ▼
Network result ready
       │
       └───────────────────────► launches = result
                                 isLoading = false

This diagram shows logical ordering, not a promise about a particular thread switch.

07 · TUTORIAL

The Runtime Is Not the Operating-System Scheduler

There are two levels of scheduling in this model.

EXECUTION DIAGRAM

SWIFT-LEVEL SCHEDULING

Which eligible task job should an executor run?

                 │
                 ▼

OPERATING-SYSTEM SCHEDULING

Which runnable thread should execute on a processor core?

The Swift Concurrency runtime deals in tasks, jobs, executors, priorities, dependencies and suspension.

The operating system deals in processes, threads and processor time.

Eventually, every executing Swift job becomes machine instructions running on a system thread scheduled onto a processor core.

Swift Concurrency does not replace threads or the operating system. It gives our program a higher-level way to express work so that the runtime can use those lower-level resources more effectively.

08 · TUTORIAL

Structured Concurrency Gives the Runtime Relationships

The runtime can do more than track separate pieces of work. Swift can also describe how tasks belong to one another.

CODE EXAMPLE

func loadDashboard() async throws -> Dashboard {
    async let launches = launchAPI.loadLaunches()
    async let weather = weatherAPI.loadWeather()
    async let missions = missionAPI.loadMissions()

    return try await Dashboard(
        launches: launches,
        weather: weather,
        missions: missions
    )
}

These three child tasks belong to the current task.

That relationship gives Swift a tree of work rather than a bag of unrelated callbacks.

EXECUTION DIAGRAM

loadDashboard task
      │
      ├── loadLaunches child task
      ├── loadWeather child task
      └── loadMissions child task

The parent cannot complete until its child tasks complete. Cancellation and errors can propagate through this structure according to the operations used.

This is a major architectural advantage.

The runtime does not merely know that four closures exist. It knows that three concurrent operations contribute to one feature-level operation named loadDashboard().

09 · TUTORIAL

A New Way to Describe an Application

A traditional description of an application often begins with objects.

CODE EXAMPLE

THE APPLICATION AS OBJECTS

AppDelegate
ViewControllers
ViewModels
Services
Managers
Repositories

Those types still matter, but they describe where code and state are stored. They do not necessarily describe how work moves through the running application.

Swift Concurrency encourages a second description:

EXECUTION DIAGRAM

THE APPLICATION AS FEATURES AND TASKS

Launch List feature
  ├── refresh launches task
  ├── load agency data task
  └── update visible state task

Launch Details feature
  ├── load mission task
  ├── load weather task
  └── schedule notification task

Account feature
  ├── restore session task
  └── synchronise subscription task

This is not a demand to rename every service as a task.

It is a way to recognise that a running application is a changing graph of work:

• some tasks are running;

• some tasks are waiting;

• some tasks have child tasks;

• some tasks are cancelled because a feature disappeared;

• and some tasks must enter an actor before accessing isolated state.

Once the codebase is viewed this way, architecture is no longer concerned only with which object owns a function.

It also asks:

• Which feature owns this task?

• How long should the task live?

• Which other tasks are its children?

• What cancels it?

• Where may it suspend?

• Which actor protects the state it changes?

💡 The Architectural Shift

A type describes where behaviour and state live.

A task describes asynchronous work with a beginning, a lifetime, possible suspension points and an end.

A feature can be understood as a coordinated collection of state and tasks.

From TaskManager Objects to Feature Lifetimes

Older architectures often introduced a custom manager for every category of asynchronous work.

CODE EXAMPLE

final class LaunchTaskManager {
    private var currentRequest: URLSessionDataTask?

    func loadLaunches(
        completion: @escaping (Result<[Launch], Error>) -> Void
    ) {
        currentRequest = makeRequest(completion: completion)
        currentRequest?.resume()
    }

    func cancel() {
        currentRequest?.cancel()
    }
}

The object manually stores the underlying operation, routes completion and exposes cancellation.

With Swift Concurrency, the task itself can represent the lifetime of the feature operation:

CODE EXAMPLE

@MainActor
final class LaunchListFeature: ObservableObject {
    @Published private(set) var launches: [Launch] = []

    private var refreshTask: Task<Void, Never>?

    func refresh() {
        refreshTask?.cancel()

        refreshTask = Task {
            do {
                launches = try await LaunchAPI().loadLaunches()
            } catch is CancellationError {
                // The previous refresh no longer owns the result.
            } catch {
                launches = []
            }
        }
    }

    func stop() {
        refreshTask?.cancel()
        refreshTask = nil
    }
}

The stored task handle is not a replacement for every manager or service. LaunchAPI still owns the networking capability, and the feature still owns its visible state.

What has changed is the description of the live operation.

The feature starts a refresh task, cancels an obsolete refresh task and accepts a result only through the task that still belongs to its current lifetime.

The architecture now reflects both static ownership and work over time.

10 · TUTORIAL

What the Runtime Does Not Decide

The runtime manages execution. It does not design the application for us.

It does not decide:

• which feature should own a task;

• when the user no longer needs a result;

• which errors should be visible;

• which state belongs inside an actor;

• whether two operations are truly independent;

• or whether expensive synchronous work should be moved away from the main actor.

The runtime provides the execution model. Architecture gives that work meaning, ownership and lifetime.

11 · TUTORIAL

Common Misconceptions

Is the Swift Concurrency runtime a thread pool?

No. It uses system execution resources underneath, but the runtime is a broader task-scheduling system. It tracks task state, suspension, priorities, dependencies, actor isolation and eligible jobs.

Does every Task receive its own thread?

No. Many tasks can share a much smaller collection of system threads because suspended tasks do not require a thread merely to preserve their wait.

Does await send the remaining function to a background thread?

No. await marks a possible suspension point. If the task suspends, its continuation later becomes eligible for an appropriate executor. Actor isolation can require that continuation to return to a particular executor.

Does the main run loop schedule every Swift task?

No. The main run loop coordinates sources of main-thread work. The Swift Concurrency runtime and its executors manage eligible task jobs. Main-actor work is where these systems must integrate with the main execution domain.

Does Swift Concurrency guarantee parallel execution?

No. Swift Concurrency allows work to make progress concurrently. Whether separate jobs execute simultaneously depends on available resources, isolation requirements and runtime scheduling.


12 · TUTORIAL

The Complete Mental Model

EXECUTION DIAGRAM

APPLICATION STARTUP
      │
      ├── Establish app lifecycle
      ├── Establish main execution system
      └── Make Swift Concurrency runtime available
                    │
                    ▼
FEATURES CREATE ASYNCHRONOUS TASKS
                    │
                    ▼
RUNTIME TRACKS EACH TASK
      │
      ├── runnable
      ├── running
      ├── suspended
      └── completed or cancelled
                    │
                    ▼
EXECUTORS ACCEPT ELIGIBLE JOBS
                    │
                    ▼
SYSTEM THREADS EXECUTE MACHINE INSTRUCTIONS
                    │
                    ▼
OPERATING SYSTEM SCHEDULES THREADS ONTO CPU CORES

The Swift Concurrency runtime sits between our task-based program and the lower-level threads used to execute it.

Our features create asynchronous operations. Tasks give those operations identity and lifetime. Suspension allows waiting tasks to stop occupying execution resources. Executors preserve scheduling and isolation requirements. System threads eventually carry each runnable job's instructions to the processor.

This is why Swift Concurrency is larger than a collection of keywords.

It adds a runtime model for the work inside our application.


13 · TUTORIAL

What to Remember

💡 What to Remember

1. The Swift Concurrency runtime manages tasks while the application is running.

2. A task represents the lifetime of an asynchronous operation. It is not a thread.

3. A runnable portion of a task can be offered to an executor as a job.

4. A suspended task does not need to block a thread while it waits.

5. await marks a possible suspension point, not a thread-switch instruction.

6. Executors arrange eligible task jobs according to their execution and isolation requirements.

7. The operating system still schedules threads onto processor cores.

8. The main run loop and the concurrency runtime are connected systems, not the same scheduler.

9. Structured concurrency gives tasks parent-and-child relationships.

10. A feature can be understood as state plus a collection of tasks with defined ownership and lifetimes.


14 · TUTORIAL

Frequently Asked Questions

What is the Swift Concurrency runtime?

The Swift Concurrency runtime is the system that tracks Swift tasks, suspension and runnable task jobs, then cooperates with executors and system threads so eligible asynchronous work can execute.

When is the Swift Concurrency runtime created?

Application developers do not manually create one runtime object. The compiler, Swift standard libraries, runtime support and platform integration make the concurrency system available to the running program.

Is the runtime connected to the main run loop?

Main-actor work must integrate with the application's main execution machinery, but the complete concurrency runtime is not a callback executed once per main run-loop cycle. Non-main task work can be scheduled independently through appropriate executors.

What is the difference between a task and a job?

A task is the complete logical asynchronous operation. A job is an eligible synchronous portion of that task that an executor can arrange to run.

Why is the runtime important to app architecture?

It gives asynchronous work identity, lifetime, hierarchy, cancellation and isolation requirements. This allows a codebase to be described not only as types and services, but also as features that own coordinated collections of tasks.


15 · TUTORIAL

Continue Learning Swift Concurrency

Read What Is the Main Thread? to understand the primary execution stream used by an iOS application.

Then continue with What Is Swift Concurrency? to connect task scheduling, suspension and actor isolation to Swift's complete concurrency model.


16 · TUTORIAL

Download Xcode Playground

The accompanying Swift Concurrency Runtime Xcode playground can turn this model into a sequence of observable experiments.

It should trace task creation, suspension, interleaving, cancellation, parent-child task structure, main-actor execution and the difference between a task and a thread.

The article explains the runtime as a system. The playground will let the reader watch task lifetimes unfold inside Xcode.

bottom of page