top of page

01 · INTRODUCTION

What Is an Executor in Swift Concurrency?

An executor is the Swift Concurrency service that accepts eligible jobs and arranges for them to execute.

That sounds simple, but the executor is one of the most important pieces of the Swift Concurrency Runtime because it sits directly between the logical work we describe with Tasks and the lower-level threads that eventually execute machine instructions.

We have already established that a Swift Task is not a thread. A Task can begin, suspend, remain unfinished for a period of time, and later continue without permanently owning one system thread for its entire lifetime.

That creates an obvious problem.

If Tasks do not own threads, then when some Task work becomes ready to execute, who decides how that eligible work gets access to an execution resource?

That is where executors enter the architecture.

💡 The Answer

An executor is a service that executes Swift concurrency jobs.

Tasks describe asynchronous work. Runnable portions of that work become jobs. Executors arrange the execution of those jobs using underlying system execution resources.

02 · TUTORIAL

Start With the Model We Already Know

We have gradually built this execution model throughout the previous articles:

EXECUTION DIAGRAM

Swift Task
     │
     ▼
Runnable Job
     │
     ▼
Executor
     │
     ▼
System Thread
     │
     ▼
OS Scheduler
     │
     ▼
CPU Core
     │
     ▼
Machine Instructions

The executor lives in the middle of this model.

Above it is the Swift Concurrency world of Tasks, asynchronous functions, actors and isolation.

Below it is the operating-system world of threads, scheduling and processors.

This is why an executor can initially be difficult to understand. It is neither the high-level Task we wrote nor the physical execution resource that ultimately runs instructions.

It is part of the machinery that connects the two.

03 · TUTORIAL

A Task Is the Whole Asynchronous Operation

Imagine this Task:

CODE EXAMPLE

Task {
    let launches = try await api.loadLaunches()
    await model.update(launches)
}

The Task represents the asynchronous operation across its entire lifetime.

That lifetime might look like this:

EXECUTION DIAGRAM

TASK

Begin
  │
  ▼
Run some work
  │
  ▼
await loadLaunches()
  │
  ▼
SUSPEND
  │
  │
  │ waiting
  │
  ▼
Become eligible again
  │
  ▼
Continue work
  │
  ▼
Finish

The Task is therefore larger than one uninterrupted period of processor execution.

It can contain several separate periods during which Swift code is actually runnable.

04 · TUTORIAL

A Job Is Runnable Work

This is why it helps to distinguish the Task from a job.

Think of a job as a schedulable unit of synchronous Swift concurrency work that is ready to execute.

Our asynchronous Task may therefore produce separate runnable regions over its lifetime.

EXECUTION DIAGRAM

TASK

JOB A
████████
       │
       ▼
     await
       │
       ▼
   SUSPENDED

       ⋮

       ⋮

   ready again
       │
       ▼
JOB B
████████
       │
       ▼
    finish

The Task represents the complete asynchronous operation.

The jobs are the pieces of that operation that can currently execute.

And once a job becomes eligible to execute, it needs somewhere to go.

05 · TUTORIAL

The Executor Accepts Eligible Jobs

This is the role of the executor.

Conceptually:

EXECUTION DIAGRAM

JOB A ───────┐
             │
JOB B ───────┼────▶ EXECUTOR
             │
JOB C ───────┘

The executor is responsible for arranging execution of the jobs that belong to its execution domain.

Eventually those jobs must execute using underlying system threads.

EXECUTION DIAGRAM

Eligible Jobs
      │
      ▼
   Executor
      │
      ▼
System Execution Resources
      │
      ▼
System Threads
      │
      ▼
CPU Cores

This is the bridge we were missing when we first learned that Tasks do not own threads.

06 · TUTORIAL

An Executor Is Not a Thread

This distinction should be made immediately and repeatedly until it becomes natural.

An executor is not a system thread.

A thread is an operating-system execution context.

An executor is part of Swift's concurrency model and is responsible for executing jobs.

So this:

CODE EXAMPLE

EXECUTOR = THREAD

is the wrong mental model.

A better model is:

EXECUTION DIAGRAM

EXECUTOR
   │
   │ arranges eligible jobs
   │ for execution
   ▼
SYSTEM EXECUTION RESOURCES
   │
   ▼
THREADS
   │
   ▼
CPU

💡 Important Idea

An executor describes where Swift concurrency jobs are allowed or expected to execute within Swift's concurrency model. It is not itself the operating-system thread that runs the machine instructions.

07 · TUTORIAL

An Executor Is Not a CPU Core Either

For the same reason, an executor is not a processor core.

The processor is hardware.

The executor is software machinery inside the concurrency system.

The relationship is layered:

EXECUTION DIAGRAM

Swift Job
    │
    ▼
Executor
    │
    ▼
System Thread
    │
    ▼
OS Scheduler
    │
    ▼
CPU Core

Once this hierarchy is clear, many confusing concurrency explanations become much easier to decode.

08 · TUTORIAL

Is an Executor a Queue?

This comparison is more tempting because executors and queues both seem to organise work.

But we should still avoid treating the words as synonyms.

Grand Central Dispatch gives us dispatch queues:

CODE EXAMPLE

DispatchQueue.main.async {
    updateUI()
}

Swift Concurrency gives us executors as part of its Task and isolation model.

Both systems can organise executable work, but they belong to different programming abstractions and have different contracts.

So rather than saying:

CODE EXAMPLE

An executor is basically a queue.

it is better to learn the real definition:

An executor is a service that executes jobs.

That definition remains useful even as Swift's executor APIs become more sophisticated.

09 · TUTORIAL

Why Do We Need Executors at All?

Imagine a concurrency runtime with Tasks but no executor concept.

We create:

CODE EXAMPLE

Task A
Task B
Task C
Task D
Task E

Task A reaches a suspension point.

Task B becomes runnable.

Task C belongs to an actor.

Task D needs MainActor isolation.

Task E is nonisolated concurrent work.

Something now needs to understand that these jobs do not all have identical execution requirements.

For example:

CODE EXAMPLE

JOB A

general concurrent work


JOB B

must respect Actor A isolation


JOB C

must respect MainActor isolation

The executor system provides the machinery through which those execution requirements can be expressed and enforced.

10 · TUTORIAL

Actors Make Executors Easier to Understand

Consider an actor:

CODE EXAMPLE

actor LaunchStore {

    private var launches: [Launch] = []

    func save(_ launch: Launch) {
        launches.append(launch)
    }
}

The actor protects its isolated mutable state.

That means Swift cannot allow two independently executing actor-isolated jobs to mutate that state simultaneously.

Conceptually, imagine two Tasks both want to call:

CODE EXAMPLE

await launchStore.save(launch)

We do not want:

EXECUTION DIAGRAM

Actor Job A
████████

Actor Job B
████████

both executing actor-isolated
state mutation simultaneously

Instead, the actor's serial executor ensures that actor-isolated jobs execute one at a time.

EXECUTION DIAGRAM

ACTOR EXECUTION

Job A
████████
        │
        ▼
Job B
        ████████
                │
                ▼
Job C
                ████████

That is one of the key mechanisms that makes actor isolation useful.

11 · TUTORIAL

Actors Use Serial Executors

This gives us an important executor category.

An actor is associated with a serial executor.

Serial means that jobs isolated to that actor are not executed simultaneously with one another.

Conceptually:

EXECUTION DIAGRAM

Actor

Job A ───────┐
Job B ───────┼────▶ Serial Executor
Job C ───────┘

                    │
                    ▼

             execute one job
             at a time

This does not mean the actor owns one permanent thread.

That distinction matters enormously.

The serial ordering requirement belongs to the executor and actor-isolation model.

It does not require:

CODE EXAMPLE

Actor A = Thread 7 forever

The actor's work can be serialised without permanently dedicating one operating-system thread to that actor.

12 · TUTORIAL

Serial Execution Does Not Mean One Permanent Thread

This is worth seeing visually.

The wrong mental model is:

EXECUTION DIAGRAM

ACTOR

      │
      ▼

THREAD 4

      │
      ▼

Actor always lives
on Thread 4 forever

The useful mental model is:

EXECUTION DIAGRAM

ACTOR JOBS

Job A
Job B
Job C

   │
   ▼

SERIAL EXECUTOR

   │
   ▼

Jobs execute
one at a time

   │
   ▼

Underlying execution resources

The actor's safety comes from its isolation and serial execution rules, not from owning a named thread.

13 · TUTORIAL

This Is a Huge Change From Thread-Based Thinking

Traditional concurrency often encouraged questions such as:

CODE EXAMPLE

Which thread owns this state?

Which queue protects this property?

Which lock should I use?

Swift actors allow us to ask a more architectural question:

CODE EXAMPLE

Which isolation domain owns this state?

Then the executor system helps Swift enforce the execution rules associated with that domain.

This is much more powerful than simply attaching a variable to a thread.

14 · TUTORIAL

Now Consider the MainActor

The most familiar global actor for an iOS developer is MainActor.

CODE EXAMPLE

@MainActor
final class LaunchModel {

    var launches: [Launch] = []

}

The type's isolated state belongs to the MainActor.

Work requiring that isolation must execute through the MainActor's executor.

On Apple platforms, this execution domain is associated with main-thread execution.

A simplified model is:

EXECUTION DIAGRAM

MainActor-isolated Job
          │
          ▼
    MainActor Executor
          │
          ▼
 Main-thread execution
          │
          ▼
     OS Scheduler
          │
          ▼
       CPU Core

This is why MainActor and the main thread are closely related while still not being the same concept.

15 · TUTORIAL

MainActor Is an Isolation Domain, Not a Thread Alias

This distinction becomes easier now that we understand executors.

The main thread belongs to the operating-system layer.

MainActor belongs to Swift's concurrency and isolation model.

Its executor provides the bridge.

EXECUTION DIAGRAM

SWIFT

@MainActor
     │
     ▼
Isolation requirement
     │
     ▼
MainActor Executor
     │
     ▼
Main-thread execution


OPERATING SYSTEM

Main Thread
     │
     ▼
OS Scheduling
     │
     ▼
CPU

This is a much richer model than simply saying:

CODE EXAMPLE

@MainActor means main thread.

That shorthand may occasionally help, but it hides the isolation architecture Swift actually gives us.

16 · TUTORIAL

What About Work That Does Not Belong to an Actor?

Not every asynchronous function is actor isolated.

Swift also has a default global concurrent executor for work that does not have a stricter executor requirement.

Conceptually:

EXECUTION DIAGRAM

Nonisolated Concurrent Work

Job A ──────────┐
Job B ──────────┤
Job C ──────────┼──▶ Global Concurrent Executor
Job D ──────────┤
Job E ──────────┘

                          │
                          ▼

               Shared execution resources

This work does not require the one-at-a-time isolation guarantee of one particular actor.

Eligible jobs can therefore participate in concurrent execution.

17 · TUTORIAL

Serial and Concurrent Are Different Requirements

Now we can compare two executor environments.

EXECUTION DIAGRAM

ACTOR SERIAL EXECUTOR

Job A
████████

        Job B
        ████████

                Job C
                ████████


GLOBAL CONCURRENT EXECUTION

Job X
████████████

Job Y
    ████████████

Job Z
        ████████████

The first model protects an actor's isolated state by ensuring actor-isolated jobs execute serially.

The second supports work that can make progress concurrently.

This is why executors are deeply connected to isolation.

18 · TUTORIAL

An Executor Does Not Necessarily Mean Immediate Execution

Suppose several jobs become eligible for one actor.

CODE EXAMPLE

Job A
Job B
Job C
Job D

The actor's executor cannot execute all four actor-isolated jobs simultaneously because that would violate the actor's serial isolation model.

Instead, they wait for opportunities to execute through that executor.

EXECUTION DIAGRAM

Eligible Actor Jobs

A
B
C
D



Serial Executor


├── execute A

├── execute B

├── execute C

└── execute D

The exact ordering of independently submitted work should not be guessed unless Swift's concurrency semantics provide an ordering guarantee for the particular situation.

The important executor guarantee here is serial execution of actor-isolated jobs, not that developers can infer arbitrary global ordering from source-code appearance.

19 · TUTORIAL

Now Add Suspension

This is where executors and suspension connect beautifully.

Imagine an actor-isolated method:

CODE EXAMPLE

actor LaunchStore {

    var launches: [Launch] = []

    func refresh() async throws {

        let newLaunches = try await api.loadLaunches()

        launches = newLaunches
    }
}

The method begins executing actor-isolated work.

EXECUTION DIAGRAM

Actor Job A

████████
       │
       ▼
     await

If the Task suspends, Job A is no longer continuously occupying the actor's executor while waiting.

That creates an opportunity for other eligible actor work to execute.

EXECUTION DIAGRAM

Actor Job A

████████
       │
       ▼
    SUSPEND


Actor Job B

        ████████


Actor Job C

                ████████


Later...

Actor Job A continuation

                        ████████

This is actor reentrancy beginning to appear in our model.

20 · TUTORIAL

The Actor Is Serial, but the Task Can Still Suspend

This can initially sound contradictory.

If an actor executes jobs serially, how can another actor job execute while the first actor method has not finished?

Because the whole asynchronous method is not one permanently executing job.

The Task can suspend.

Its current job finishes its period of synchronous execution.

Other eligible actor jobs can then execute through the serial executor.

Later, the original Task's continuation can become another eligible actor-isolated job.

Conceptually:

EXECUTION DIAGRAM

TASK A

Job A1
██████
     │
     ▼
  suspend

     ⋮

     ⋮

Job A2
               ██████


ACTOR EXECUTOR

Job A1
██████

      Job B
      ██████

            Job C
            ██████

                  Job A2
                  ██████

The actor never had two isolated jobs executing simultaneously.

But other actor work made progress while Task A was suspended.

21 · TUTORIAL

This Is Why State Can Change Across await

Now actor reentrancy stops sounding mysterious.

Consider:

CODE EXAMPLE

actor LaunchStore {

    var selectedID: Int?

    func select(_ id: Int) async throws {

        selectedID = id

        let details = try await loadDetails(id)

        save(details)
    }
}

Before the await:

CODE EXAMPLE

selectedID = id

Task A is executing actor-isolated work.

Then it suspends.

Another actor-isolated job can execute through the same serial executor and potentially change selectedID.

When Task A later continues, the actor is still data-race safe, but the logical state may no longer match assumptions made before suspension.

The executor has protected simultaneous actor-isolated access.

It has not frozen the actor's state for the entire lifetime of every asynchronous method call.

22 · TUTORIAL

Executors Protect Execution Rules, Not Business Logic

This is an important distinction.

The executor can enforce a rule such as:

CODE EXAMPLE

Only one job isolated to this actor
executes at a time.

But it cannot understand a product rule such as:

CODE EXAMPLE

If the user selected Rocket 42
before the network request,
they must still have Rocket 42
selected afterward.

That is application logic.

Swift can protect us from simultaneous unsafe actor-isolated access.

We still have to reason about state changes across suspension points.

23 · TUTORIAL

What Happens When a Job Is Ready to Run?

Suppose a suspended Task becomes ready to continue.

Its continuation now represents eligible work.

EXECUTION DIAGRAM

Task A

SUSPENDED

    │
    │ awaited operation completes
    ▼

CONTINUATION READY

    │
    ▼

Eligible Job

That job must execute on an appropriate executor.

If it belongs to an actor:

EXECUTION DIAGRAM

Eligible Actor Job
       │
       ▼
Actor's Serial Executor

If it belongs to the MainActor:

EXECUTION DIAGRAM

MainActor Job
      │
      ▼
MainActor Executor

If it has no actor isolation requirement:

EXECUTION DIAGRAM

Nonisolated Job
      │
      ▼
Global Concurrent Executor

The executor requirement therefore tells Swift something very important about where that runnable work belongs.

24 · TUTORIAL

Then How Does the Executor Reach a Thread?

Eventually the work must leave the abstract Swift concurrency model and become actual machine execution.

Conceptually:

EXECUTION DIAGRAM

Eligible Swift Job
        │
        ▼
     Executor
        │
        ▼
Runtime execution machinery
        │
        ▼
System Thread
        │
        ▼
OS Scheduler
        │
        ▼
CPU Core
        │
        ▼
Machine Instructions

The exact runtime implementation is more sophisticated than this teaching diagram, and Swift allows custom executors in modern versions of the language.

But the important architectural distinction remains stable:

The executor belongs to Swift's concurrency model.

The thread belongs to the operating-system execution model.

25 · TUTORIAL

One Executor Can Serve Many Tasks

This is another reason an executor should not be imagined as one Task's personal thread.

Imagine several Tasks all need MainActor isolation.

EXECUTION DIAGRAM

Task A ─────┐
            │
Task B ─────┼────▶ MainActor Executor
            │
Task C ─────┤
            │
Task D ─────┘

The executor coordinates eligible jobs from those Tasks according to the MainActor's execution rules.

Likewise, many Tasks performing nonisolated concurrent work can share the default global concurrent execution resources.

EXECUTION DIAGRAM

Task A ─────┐
Task B ─────┤
Task C ─────┼────▶ Global Concurrent Executor
Task D ─────┤
Task E ─────┘

The architecture is therefore many-to-few rather than one-to-one.

26 · TUTORIAL

This Is Similar to the Lesson We Learned About Threads

Earlier we discovered that having one thousand asynchronous operations does not mean we need one thousand threads.

Now we can extend that principle.

EXECUTION DIAGRAM

Many Tasks

        │
        ▼

Many eligible Jobs

        │
        ▼

Executors

        │
        ▼

Managed execution resources

        │
        ▼

A limited number of threads

        │
        ▼

Finite CPU cores

Each layer reduces the need for application code to directly manage the layer below it.

This is exactly what a good abstraction should do.

27 · TUTORIAL

Executors Are About Eligibility and Isolation

There is a useful shift in vocabulary happening here.

Traditional thread-focused thinking asks:

CODE EXAMPLE

Which thread should this function run on?

Swift Concurrency increasingly encourages:

CODE EXAMPLE

What isolation does this work require?

Which executor is responsible
for eligible work in that domain?

This is a more architectural way to reason about concurrent software.

We describe ownership and isolation first.

The runtime deals with much of the underlying execution machinery.

28 · TUTORIAL

The Default Global Concurrent Executor

Swift also needs somewhere for ordinary concurrent work that has no specific actor-executor requirement.

The runtime provides a default global concurrent executor shared by work without stricter executor requirements.

Conceptually:

EXECUTION DIAGRAM

Nonisolated Job A ──────┐
                        │
Nonisolated Job B ──────┤
                        │
Nonisolated Job C ──────┼──▶ GLOBAL CONCURRENT EXECUTOR
                        │
Nonisolated Job D ──────┘
                                  │
                                  ▼
                         Managed execution

This is another important difference from an actor's serial executor.

The global executor supports concurrent progress for work that is not constrained by one actor's isolation domain.

29 · TUTORIAL

Modern Swift Also Supports Custom Executors

As Swift's concurrency system has evolved, executors have become an increasingly explicit part of the language and runtime.

Swift provides executor protocols, including serial executors and task executors, and modern Swift can express custom executor requirements for specialised execution environments.

You do not need to build a custom executor in order to understand Swift Concurrency.

For most application code, the important concepts remain:

CODE EXAMPLE

MainActor executor

Actor serial executors

Global concurrent execution

Task executor preferences
for specialised cases

But knowing that executors are a real runtime abstraction rather than merely a teaching metaphor is useful.

30 · TUTORIAL

Do I Need to Choose an Executor Every Time I Create a Task?

No.

Ordinary Swift application code normally expresses isolation and concurrency requirements using language features such as:

CODE EXAMPLE

@MainActor

actor

nonisolated

Task

async let

withTaskGroup

Swift then derives the appropriate execution requirements from those declarations and the surrounding isolation context.

The fact that executors exist does not mean every developer should start manually selecting executors throughout their application.

The goal is understanding.

Once we know the executor layer exists, the behavior of actors, MainActor and suspended Task continuations becomes much easier to explain.

31 · TUTORIAL

Executors Do Not Make CPU Work Free

We should carry forward the lesson from the previous article.

Suppose a job begins executing:

CODE EXAMPLE

for item in oneMillionItems {
    performExpensiveCalculation(item)
}

The executor has successfully arranged for that job to execute.

But the executor does not magically divide the synchronous loop into tiny perfectly fair pieces.

If that synchronous region runs for a long time, it continues occupying its execution resource during that period.

This is why cooperative concurrency still requires good application architecture.

The executor can choose among eligible jobs.

It cannot make synchronous work that is already executing become inexpensive simply because other Tasks would also like to make progress.

💡 Remember

An executor gives eligible jobs opportunities to execute. It does not automatically time-slice arbitrary synchronous Swift code into tiny pieces.

32 · TUTORIAL

Now Bring Back Our Animation Frames

This fits directly into the responsive-app mental model we have been developing.

Imagine our application has several eligible pieces of work.

EXECUTION DIAGRAM

TIME ─────────────────────────────────────────────▶

UI / MainActor

██          ██          ██          ██


Task A

    ████          ████


Task B

          ████          ████


Task C

                ████

This is conceptual rather than a literal Swift scheduling diagram.

But it expresses what we want from the architecture.

Our application's different pieces of work can make progress over time without one unnecessarily monopolising execution resources required by everything else.

Executors are part of the machinery that allows eligible Swift jobs to participate in that model.

33 · TUTORIAL

The Executor Is the Gateway to Execution

There is a useful way to summarise everything we have learned.

A Task describes asynchronous work.

A job represents a runnable portion of that work.

An executor provides the execution service for eligible jobs.

A system thread provides the operating-system execution context.

The OS scheduler decides when that thread receives processor time.

The CPU executes the machine instructions.

So:

EXECUTION DIAGRAM

TASK
"What asynchronous work exists?"

      │
      ▼

JOB
"What part is runnable now?"

      │
      ▼

EXECUTOR
"Where is this eligible Swift work
allowed / arranged to execute?"

      │
      ▼

THREAD
"What OS execution context
is running instructions?"

      │
      ▼

CPU CORE
"What machine instruction
executes now?"

Those are different layers answering different questions.

34 · TUTORIAL

Why This Matters to an iOS Developer

Without executors, several common Swift Concurrency statements remain vague.

Why does an actor prevent simultaneous access to its isolated state?

Because actor-isolated jobs execute through that actor's serial executor.

Why can another actor operation run while one actor method is suspended?

Because the suspended Task is no longer executing its current job, allowing another eligible actor job to execute through the serial executor.

Why is MainActor related to the main thread without simply being another name for the main thread?

Because MainActor is a Swift isolation domain whose executor connects that isolation requirement to main-thread execution.

Why doesn't every Task need its own thread?

Because eligible Task jobs are coordinated through executors and can share underlying execution resources.

Why can Swift Concurrency be cooperative while the OS scheduler remains preemptive?

Because executors and Task scheduling exist at one layer while system-thread scheduling exists underneath them.

Again, several apparently disconnected rules become one architecture.

35 · TUTORIAL

What to Remember

💡 What to Remember

An executor is a service that executes Swift concurrency jobs.

A Task represents an asynchronous operation across its lifetime. A job represents schedulable work that is currently eligible to execute.

Executors sit between eligible Swift concurrency jobs and the lower-level system execution resources that ultimately run machine instructions.

An executor is not a system thread.

An executor is not a CPU core.

An executor should not simply be treated as another name for a Grand Central Dispatch queue.

Actors use serial executors so actor-isolated jobs execute one at a time.

Serial actor execution does not mean an actor permanently owns one specific system thread.

The MainActor is a Swift isolation domain whose executor is associated with main-thread execution.

Work without stricter actor isolation requirements can use Swift's global concurrent executor.

When a Task suspends, other eligible jobs can use the relevant executor. When the suspended Task becomes ready again, its continuation can become another eligible job.

Executors enforce execution requirements such as actor serialization, but they do not freeze business state across an await.

Executors also do not automatically divide long synchronous computations into small time slices. Cooperative code still requires us to structure expensive work sensibly.

The complete model is:

EXECUTION DIAGRAM

Task
 ↓
Job
 ↓
Executor
 ↓
System Thread
 ↓
OS Scheduler
 ↓
CPU Core
 ↓
Machine Instructions

36 · TUTORIAL

Your Next Move

We now understand what an executor is and why Swift Concurrency needs this layer between Tasks and system threads.

Tasks describe asynchronous work.

Jobs represent runnable pieces of that work.

Executors arrange eligible jobs for execution.

Threads eventually carry those machine instructions onto processor cores.

But executors introduce another important question.

An actor uses a serial executor.

That executor ensures that actor-isolated jobs do not execute simultaneously.

So what exactly happens when several Tasks all want access to the same actor at the same time?

Do they form a queue?

Which one executes first?

What happens when the currently executing actor method reaches an await?

Can another Task enter the actor before the original method finishes?

And if so, how can actors still protect mutable state?

Those questions take us directly into one of the most important concepts in actor-based programming:

actor isolation and reentrancy.

The next article should be:

How Does an Actor Execute Multiple Tasks Safely?

bottom of page