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.
