top of page

01 · INTRODUCTION

What Are the Benefits of Swift Concurrency?

Swift Concurrency gives iOS developers a safer, more structured and more scalable way to organise asynchronous and concurrent work. It helps applications remain responsive, reduces the amount of manual thread and queue management we need to perform, gives mutable state explicit isolation, allows the compiler to detect many concurrency mistakes, and gives asynchronous work a structure through Tasks, child Tasks, cancellation and error propagation.

The biggest benefit is therefore not that Swift Concurrency simply makes code execute faster.

It changes how we architect work.

Instead of constructing applications around threads, callbacks, queues, locks and conventions that every developer must remember, we can increasingly describe:

CODE EXAMPLE

What work needs to happen?

What work can happen concurrently?

What state belongs together?

Who owns that mutable state?

Where may a Task suspend?

Which operations depend on one another?

When should unfinished work be cancelled?

What values may safely cross
between concurrency domains?

Swift and the Swift Concurrency Runtime can then help enforce those decisions.

💡 The Big Benefit

Swift Concurrency moves concurrency from a collection of manually enforced threading techniques toward a language-level model of Tasks, suspension, structured lifetimes and isolated state.

The result can be code that is easier to understand, easier to maintain and substantially harder to make accidentally unsafe.

02 · TUTORIAL

Swift Concurrency Gives Us a Better Abstraction Than Threads

Before Swift Concurrency, iOS developers frequently discussed asynchronous work in terms of threads and queues.

We might say:

CODE EXAMPLE

Move this to a background queue.

Dispatch this back to main.

Protect this property with a serial queue.

Create another operation.

Add a lock here.

Those techniques remain part of the history and foundations of concurrent programming, and lower-level threads still exist underneath Swift Concurrency.

But a thread is an operating-system execution resource. It is not necessarily the best abstraction for describing the architecture of one of our application features.

When we load a profile, download ten images or refresh three APIs, what we actually have is work.

Swift Concurrency lets us describe that work as Tasks and asynchronous functions instead of requiring the application architecture to revolve around creating and managing threads directly.

EXECUTION DIAGRAM

OLD MENTAL MODEL

Application Work
       │
       ▼
Which Queue?
       │
       ▼
Which Thread?
       │
       ▼
How Do I Synchronize It?


SWIFT CONCURRENCY

Application Work
       │
       ▼
Task / async operation
       │
       ▼
Isolation + Structure
       │
       ▼
Swift Concurrency Runtime
       │
       ▼
Underlying Threads

The threads have not disappeared.

We have simply gained a much better level at which to design our applications.

03 · TUTORIAL

Benefit: Asynchronous Code Can Read From Top to Bottom

One of the most immediately visible improvements is the way asynchronous code can be written.

A callback-based API might require us to write:

CODE EXAMPLE

api.loadLaunches { result in
    switch result {
    case .success(let launches):
        DispatchQueue.main.async {
            self.launches = launches
        }

    case .failure(let error):
        DispatchQueue.main.async {
            self.errorMessage = error.localizedDescription
        }
    }
}

Nothing about this code is inherently broken.

But the logical operation has been split across closures, result handling and dispatch calls.

With an async API, the same operation can often be expressed more directly:

CODE EXAMPLE

@MainActor
func refresh() async {
    do {
        launches = try await api.loadLaunches()
    } catch {
        errorMessage = error.localizedDescription
    }
}

The code now reads much more closely to the actual intention:

EXECUTION DIAGRAM

load launches
      ↓
wait for result
      ↓
store launches

or

receive error
      ↓
store error state

This is not merely prettier syntax.

Readable execution structure makes asynchronous behaviour easier to reason about during development, debugging and code review.

04 · TUTORIAL

Benefit: Waiting Does Not Need to Mean Blocking a Thread

This is one of the most important architectural advantages.

Imagine:

CODE EXAMPLE

let launches = try await api.loadLaunches()

The network request may take half a second.

Our processor does not need to execute half a second of instructions to make a remote server respond.

The operation spends much of its lifetime waiting.

With Swift's asynchronous Task model, the current Task can potentially suspend while it waits.

EXECUTION DIAGRAM

Task A

████████
       │
       ▼
     await
       │
       ▼
    SUSPEND

       ⋮

       ⋮

result ready
       │
       ▼

Task A continues

                  ████████

The Task remains unfinished.

But a system thread does not need to remain permanently dedicated to that suspended Task merely to represent the fact that the operation has not completed yet.

This allows underlying execution resources to be used by other eligible work. Swift's concurrency APIs are built around Tasks, executors and jobs rather than a one-Task-to-one-thread model. citeturn754324search0turn754324search8

05 · TUTORIAL

Benefit: Our UI Can Remain Responsive While Work Continues

For an iOS developer, this is one of the benefits the user can actually feel.

At 60 frames per second, a frame opportunity occurs approximately every 16.7 milliseconds.

A synchronous operation that monopolises main-thread execution for 200 milliseconds spans roughly twelve 60 Hz frame intervals.

Even if the calculation itself is impressively fast when measured in isolation, the application can feel terrible.

Swift Concurrency gives us a model in which asynchronous work can remain unfinished without requiring MainActor execution to remain blocked for the entire lifetime of that operation.

EXECUTION DIAGRAM

CONCEPTUAL MODEL

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


Refresh Task

████
   │
   ▼
SUSPEND
   ⋮
   ⋮
   ⋮
                              ████
                              resume


MainActor / UI Work

    ██    ██    ██    ██    ██

The Task can make progress.

The UI can make progress.

Other Tasks can make progress.

This is much closer to the kind of concurrency we actually want in an interactive application.

06 · TUTORIAL

Benefit: Concurrency Becomes About Progress Rather Than Hogging an Execution Resource

Consider a large piece of application work.

CODE EXAMPLE

for item in items {
    process(item)
}

Perhaps the complete computation takes 200 milliseconds.

The old instinct can easily become:

CODE EXAMPLE

I have execution now.

Finish absolutely everything.

A concurrent application should instead make us ask:

CODE EXAMPLE

Does all of this work
need to finish before anything
else may make progress?

Sometimes it does.

Often it does not.

Where an algorithm permits it, large workloads can be architected as meaningful units of work that cooperate with other work in the application.

EXECUTION DIAGRAM

CONCEPTUAL MODEL

Task A

████        ████        ████


Task B

    ████            ████


UI Work

        ██      ██        ██

Swift Concurrency does not automatically chop arbitrary synchronous code into time slices for us.

But it gives us an execution model in which Tasks, suspension and explicit cooperative mechanisms make this style of architecture possible.

07 · TUTORIAL

Benefit: Many Tasks Do Not Require Many Dedicated Threads

Imagine an application with one thousand asynchronous operations.

A primitive model might imply:

CODE EXAMPLE

1000 asynchronous operations

           =

1000 system threads

That would be an expensive relationship.

Threads require operating-system resources, stack space and scheduling overhead. Creating more threads also does not create more CPU cores.

Swift Tasks are not system threads.

That lets many logical asynchronous operations exist while the runtime coordinates runnable work over the available underlying execution resources.

EXECUTION DIAGRAM

Task 1
Task 2
Task 3
Task 4
Task 5
...
Task 1000

     │
     ▼

Swift Concurrency Runtime

     │
     ▼

Executors / Jobs

     │
     ▼

Managed System Threads

     │
     ▼

Finite CPU Cores

This separation between how much work exists and how many threads exist is one of the foundations of the system.

08 · TUTORIAL

Benefit: Swift Can Protect Shared Mutable State With Actors

One of the hardest problems in concurrent programming has always been shared mutable state.

Consider:

CODE EXAMPLE

final class Counter {
    var value = 0

    func increment() {
        value += 1
    }
}

If multiple execution contexts mutate value concurrently without synchronization, we can create a data race.

Swift gives us actors:

CODE EXAMPLE

actor Counter {

    private(set) var value = 0

    func increment() {
        value += 1
    }
}

The actor establishes an isolation domain around its mutable state.

Actor-isolated execution is serialized so that multiple actor-isolated jobs do not execute simultaneously against the same actor instance. The Swift language documentation describes actors as allowing only one Task at a time to access their mutable state. citeturn754324search6

EXECUTION DIAGRAM

Task A ─────┐
            │
Task B ─────┼────▶ Counter Actor
            │            │
Task C ─────┘            ▼
                    Serial Execution
                         │
                         ▼
                       value

Instead of manually inventing a synchronization convention around every mutable resource, we can express the ownership directly in Swift.

09 · TUTORIAL

Benefit: Safety Rules Move From Team Memory Into the Language

This is a very significant improvement for commercial code bases.

Before actors, a team might establish a rule such as:

CODE EXAMPLE

Never access UserStore directly.

Always go through
userStoreQueue.

That can be a perfectly reasonable design.

But the rule may live in documentation, comments, code review and the memories of experienced developers.

A new developer can accidentally bypass it.

With actor isolation, Swift understands the isolation boundary.

EXECUTION DIAGRAM

OLD

Shared State
    │
    ▼
Serial Queue / Lock
    │
    ▼
Developer Convention


NEW

Actor State
    │
    ▼
Actor Isolation
    │
    ▼
Compiler-checked access rules

This means part of the concurrency architecture becomes something the compiler can reason about rather than an informal agreement the team hopes everybody remembers.

10 · TUTORIAL

Benefit: Swift Can Detect Many Data-Race Risks Before the App Runs

This is one of Swift Concurrency's strongest benefits.

Historically, concurrency bugs could be extremely difficult to reproduce.

A race might happen once in a thousand executions.

It might happen only on one device.

It might disappear when logging is added because the timing changes.

Swift's isolation rules and strict concurrency checking allow many unsafe transfers and isolation violations to become compiler diagnostics rather than intermittent runtime behaviour.

Swift 6's concurrency checking is specifically designed to provide stronger protection against data races in concurrent Swift code. Apple's concurrency documentation includes migration guidance for adopting the full concurrency protection of Swift 6 language mode. citeturn754324search0turn754324search1

This does not mean Swift can prove that all of our business logic is correct.

It means an important category of concurrency correctness has moved closer to compile time.

11 · TUTORIAL

Benefit: Sendable Makes Crossing Concurrency Boundaries Explicit

Once we isolate state, another question appears:

What values may safely move between those isolation domains?

That is where Sendable becomes important.

CODE EXAMPLE

struct Launch: Sendable {
    let id: Int
    let name: String
}

A sendable value can safely cross concurrency boundaries according to Swift's sendability rules.

EXECUTION DIAGRAM

Isolation Domain A

      Launch
        │
        ▼

============================
   CONCURRENCY BOUNDARY
============================

        │
        ▼

Isolation Domain B

Apple describes Sendable as representing values that can be shared across concurrent contexts without introducing a risk of data races. Its semantic requirements are checked by the compiler. citeturn754324search2

Again, the language is being given information that previously existed largely in architecture diagrams and developer assumptions.

12 · TUTORIAL

Benefit: MainActor Gives UI State a Clear Owner

Every iOS developer knows the old rule:

CODE EXAMPLE

Update UI on the main thread.

With Swift Concurrency, we can express UI-facing ownership as isolation.

CODE EXAMPLE

@MainActor
final class LaunchModel {

    var launches: [Launch] = []
    var isLoading = false
    var errorMessage: String?
}

This says something much stronger than:

CODE EXAMPLE

Remember to call
DispatchQueue.main.async
whenever these properties change.

The state itself belongs to the MainActor isolation domain.

Apple defines MainActor as a global singleton actor whose executor is equivalent to the main dispatch queue. citeturn754324search5

This gives UI-facing mutable state an explicit concurrency owner.

13 · TUTORIAL

Benefit: Structured Concurrency Gives Tasks Lifetimes

Concurrency becomes much easier to reason about when asynchronous work has structure.

Imagine a refresh operation needs launches and rockets:

CODE EXAMPLE

func loadDashboard() async throws -> Dashboard {

    async let launches = api.loadLaunches()
    async let rockets = api.loadRockets()

    return try await Dashboard(
        launches: launches,
        rockets: rockets
    )
}

The two child operations belong to the surrounding asynchronous operation.

Conceptually:

EXECUTION DIAGRAM

loadDashboard()

      │
      ├──── child: loadLaunches()
      │
      └──── child: loadRockets()
      │
      ▼
wait for required children
      │
      ▼
return Dashboard

This is different from launching unrelated background operations whose lifetimes are disconnected from the function that needed them.

Structured concurrency gives our asynchronous code a tree-shaped lifetime.

Parents and children have relationships.

Those relationships give Swift more information about when work belongs, when it should finish, and how errors and cancellation can propagate.

14 · TUTORIAL

Benefit: Fixed Concurrent Work Can Be Expressed Directly With async let

Suppose we need three independent pieces of data:

CODE EXAMPLE

async let launches = api.loadLaunches()
async let rockets = api.loadRockets()
async let astronauts = api.loadAstronauts()

let result = try await (
    launches,
    rockets,
    astronauts
)

We can express that the operations may progress concurrently without manually creating queues, dispatch groups and callback bookkeeping.

EXECUTION DIAGRAM

loadLaunches() ──────────────┐
                             │
loadRockets() ───────────────┼──▶ await results
                             │
loadAstronauts() ────────────┘

This makes the concurrency relationship visible in the structure of the Swift code itself.

15 · TUTORIAL

Benefit: Dynamic Concurrent Work Can Be Structured With Task Groups

Sometimes we do not know the number of operations until runtime.

Imagine loading details for a collection of missions.

CODE EXAMPLE

func loadDetails(
    for ids: [Int]
async throws -> [MissionDetail] {

    try await withThrowingTaskGroup(
        of: MissionDetail.self
    ) { group in

        for id in ids {
            group.addTask {
                try await api.loadMission(id: id)
            }
        }

        var details: [MissionDetail] = []

        for try await detail in group {
            details.append(detail)
        }

        return details
    }
}

The work can progress concurrently while remaining inside a structured scope.

When the scope finishes, its child-Task relationship has a defined lifetime rather than becoming an uncontrolled collection of background operations.

Apple exposes Task groups specifically for dynamically created child Tasks within a scope. citeturn754324search0

16 · TUTORIAL

Benefit: Errors Fit Naturally Into Async Code

Swift's existing error model works naturally with asynchronous functions.

CODE EXAMPLE

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

We do not need a separate concurrency-specific error system.

The same language constructs remain useful:

CODE EXAMPLE

do {
    try await refresh()
} catch {
    print(error)
}

And with structured child Tasks, errors can participate in the structure of the surrounding operation instead of being manually routed through deeply nested callback chains.

17 · TUTORIAL

Benefit: Cancellation Becomes Part of the Task Model

Application work often becomes irrelevant before it finishes.

The user leaves the screen.

A new search query replaces the previous one.

A parent operation fails.

The application no longer needs a result.

Swift Tasks have a cancellation model.

CODE EXAMPLE

func processLaunches(
    _ launches: [Launch]
async throws {

    for launch in launches {
        try Task.checkCancellation()

        process(launch)
    }
}

Cancellation is cooperative rather than a mechanism that blindly destroys arbitrary execution halfway through an instruction sequence.

That gives application code an opportunity to stop doing work that no longer matters while retaining control over cleanup and correctness.

18 · TUTORIAL

Benefit: Parent and Child Work Can Share Cancellation

Structured concurrency makes cancellation especially useful because child work belongs to a larger operation.

EXECUTION DIAGRAM

Parent Task

    │
    ├── Child A
    ├── Child B
    └── Child C

If the parent operation no longer makes sense, its structured children are not simply mysterious detached operations floating around somewhere in the application.

The structure gives the runtime and our code a meaningful relationship through which cancellation can propagate.

This is a significant improvement over concurrency designs where developers must manually track every outstanding callback or operation token.

19 · TUTORIAL

Benefit: Concurrent Code Can Express Ownership

One of the deepest benefits is that Swift Concurrency encourages us to ask:

CODE EXAMPLE

Who owns this state?

rather than merely:

CODE EXAMPLE

Which queue should I use?

Consider:

CODE EXAMPLE

actor LaunchStore {

    private var launches: [Launch] = []

    func replace(with launches: [Launch]) {
        self.launches = launches
    }
}

The actor tells us where the state belongs.

That ownership becomes part of the program's concurrency architecture.

This is easier to reason about than a large application containing dozens of unrelated mutable properties with synchronization policies scattered across queues, locks and comments.

20 · TUTORIAL

Benefit: Isolation Can Exist at the Correct Architectural Level

Different parts of an application need different isolation domains.

UI-facing state may belong to MainActor:

CODE EXAMPLE

@MainActor
final class LaunchModel {
    var launches: [Launch] = []
}

A mutable cache might belong to its own actor:

CODE EXAMPLE

actor LaunchCache {
    private var cache: [Int: Launch] = [:]
}

Another immutable value may need no actor at all:

CODE EXAMPLE

struct LaunchID: Sendable {
    let value: Int
}

We do not need to force one global synchronization mechanism across the entire application.

We can model ownership around the actual architecture of the product.

21 · TUTORIAL

Benefit: Independent Actors Can Still Make Progress Concurrently

An actor serialises isolated jobs for one actor instance.

That does not mean every actor throughout the application shares one giant serial queue.

EXECUTION DIAGRAM

Actor A

Job A1
████████

        Job A2
        ████████


Actor B

    Job B1
    █████████

                 Job B2
                 █████████

Each actor protects its own isolation domain while the wider application remains concurrent.

This gives us a useful combination:

CODE EXAMPLE

CONCURRENCY

Many independent pieces
of work can progress


+

ISOLATION

Mutable state that must not
be accessed simultaneously
can be protected

That combination is much closer to what large applications actually need.

22 · TUTORIAL

Benefit: Actor Suspension Does Not Require Holding the Actor Forever

An asynchronous actor method can suspend.

CODE EXAMPLE

actor LaunchStore {

    private var launches: [Launch] = []

    func refresh() async throws {

        let result = try await api.loadLaunches()

        launches = result
    }
}

If the Task suspends at await, the actor does not need to remain uselessly reserved for that entire network wait.

EXECUTION DIAGRAM

Task A — Actor Job

████████
       │
       ▼
    SUSPEND


        Task B — Actor Job

        ████████


                Task A Continuation

                ████████

Other eligible actor work can make progress while the original Task is suspended.

The actor retains serial execution of isolated jobs without forcing one asynchronous Task to monopolise the actor for its entire lifetime.

23 · TUTORIAL

Benefit: Swift Concurrency Gives the Compiler More Information

This may be one of the most important benefits when working in a team.

Compare these two architectures:

CODE EXAMPLE

Architecture A

"Everybody knows this property
must only be touched from queue X."


Architecture B

"This type is @MainActor."

"This state belongs to this actor."

"This value is Sendable."

The second architecture contains concurrency information in declarations the compiler understands.

This improves documentation, code review and correctness simultaneously.

A developer joining the project does not need to discover every concurrency rule solely by reading old implementation details.

24 · TUTORIAL

Benefit: Concurrency Intent Becomes Visible in Function Signatures

Consider:

CODE EXAMPLE

func loadLaunches() async throws -> [Launch]

Without opening the implementation, we already know several useful things.

The operation is asynchronous.

It can throw.

Calling it from asynchronous code requires acknowledging the potential suspension point with await.

Now consider:

CODE EXAMPLE

@MainActor
func update(with launches: [Launch])

The isolation requirement is also visible.

Concurrency behaviour is becoming part of the API contract instead of being hidden inside implementation details such as:

CODE EXAMPLE

DispatchQueue.main.async { ... }

25 · TUTORIAL

Benefit: await Makes Suspension Boundaries Visible

This deserves particular attention.

When we see:

CODE EXAMPLE

let launches = try await api.loadLaunches()

the source code visibly tells us:

CODE EXAMPLE

Execution may suspend here.

That matters because time may pass.

Other permitted work may execute.

Actor state may legitimately change.

The operation may be cancelled.

By making suspension points visible, Swift gives developers an important place to stop and reconsider assumptions.

26 · TUTORIAL

Benefit: The Compiler Can Help Us Find Isolation Mistakes During Refactoring

Imagine a large application whose concurrency architecture exists mainly as GCD conventions.

Moving a property, changing a callback or adding another execution path can accidentally bypass the established synchronization design.

With actor isolation, MainActor annotations and sendability requirements, changing the architecture can cause compiler errors at the places whose assumptions are no longer valid.

This can make large refactors safer because the concurrency relationships are represented in the type and isolation system rather than only in runtime behaviour.

27 · TUTORIAL

Benefit: Code Review Becomes More About Architecture Than Thread Guessing

Imagine reviewing old concurrent code:

CODE EXAMPLE

queue.async {
    service.load { result in
        otherQueue.async {
            cache.write(result)

            DispatchQueue.main.async {
                completion(result)
            }
        }
    }
}

A reviewer has to reconstruct the execution model.

Which queue are we currently on?

Is otherQueue serial?

Can something else access the cache?

Does completion always execute on main?

Swift Concurrency does not eliminate all reasoning, but it lets more of those rules become explicit:

CODE EXAMPLE

@MainActor
final class LaunchModel {

    private let cache: LaunchCache

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

        await cache.store(launches)

        self.launches = launches
    }
}

The reviewer can reason in terms of isolation and asynchronous boundaries instead of reverse-engineering a web of queues.

28 · TUTORIAL

Benefit: Swift Concurrency Scales Better With Modern Hardware

Modern Apple devices contain multiple processor cores.

Where work is independent and suitable for concurrent execution, the runtime and operating system can make use of available execution resources and parallel hardware.

EXECUTION DIAGRAM

CONCEPTUAL MODEL

Task A ─────▶ eligible work ─────▶ Core 1

Task B ─────▶ eligible work ─────▶ Core 2

Task C ─────▶ eligible work ─────▶ Core 3

But the important benefit is that application architecture does not need to map every Task manually to a physical core or dedicated thread.

Swift works with executors and Tasks, while the OS remains responsible for lower-level thread scheduling.

This separation lets the runtime adapt execution to the available system resources rather than hard-coding one thread strategy into every feature.

29 · TUTORIAL

Benefit: Concurrency Can Improve Throughput

Suppose three independent requests each take approximately one second.

A purely sequential implementation could behave conceptually like:

EXECUTION DIAGRAM

Request A
████████████

            Request B
            ████████████

                        Request C
                        ████████████

Total elapsed time:
approximately 3 seconds

If the operations are genuinely independent and can progress concurrently:

EXECUTION DIAGRAM

Request A
████████████

Request B
████████████

Request C
████████████

Elapsed time can approach
the longest individual operation
rather than the sum of all three.

This is one of the practical performance benefits of concurrency.

But we should be precise:

💡 Concurrency Does Not Automatically Mean Faster

Concurrency can improve responsiveness and throughput when independent work overlaps effectively.

It does not guarantee that every concurrent implementation finishes faster than every sequential implementation.

Coordination has costs, dependencies may force sequential execution, and CPU-heavy workloads remain constrained by available hardware.

30 · TUTORIAL

Benefit: We Can Choose Sequential or Concurrent Execution Explicitly

Not everything should be concurrent.

Sometimes operation B genuinely depends on operation A:

CODE EXAMPLE

let user = try await api.loadUser()
let orders = try await api.loadOrders(for: user.id)

This dependency is naturally sequential.

Other operations may be independent:

CODE EXAMPLE

async let profile = api.loadProfile()
async let news = api.loadNews()

Swift lets the structure of the source code communicate the difference.

This makes concurrency an intentional architectural decision rather than an accidental consequence of which queue happened to receive a closure.

31 · TUTORIAL

Benefit: The Code Can Better Describe the Product

This benefit is easy to underestimate.

Imagine a product requirement:

CODE EXAMPLE

When the dashboard opens:

Load launches
Load rockets
Load astronaut information

Allow independent requests
to progress concurrently.

If the user leaves,
cancel unnecessary work.

Update UI state safely.

Swift Concurrency gives us language constructs that directly correspond to those requirements.

Tasks represent work.

Structured child Tasks represent relationships.

Cancellation represents abandoned work.

Actors represent isolated ownership.

MainActor represents UI-facing isolation.

Sendable describes values that can safely cross concurrency domains.

The concurrency architecture can therefore begin to resemble the product architecture.

32 · TUTORIAL

Benefit: A Complete Feature Can Have One Coherent Concurrency Story

Consider a small application-shaped example.

CODE EXAMPLE

struct Launch: Sendable {
    let id: Int
    let name: String
}

actor LaunchCache {

    private var launches: [Launch] = []

    func store(_ launches: [Launch]) {
        self.launches = launches
    }
}

@MainActor
final class LaunchModel {

    private let api: LaunchAPI
    private let cache: LaunchCache

    private(set) var launches: [Launch] = []
    private(set) var isLoading = false
    private(set) var errorMessage: String?

    init(
        api: LaunchAPI,
        cache: LaunchCache
    ) {
        self.api = api
        self.cache = cache
    }

    func refresh() async {

        isLoading = true
        errorMessage = nil

        defer {
            isLoading = false
        }

        do {
            let launches = try await api.loadLaunches()

            try Task.checkCancellation()

            await cache.store(launches)

            self.launches = launches
        } catch is CancellationError {
            return
        } catch {
            errorMessage = error.localizedDescription
        }
    }
}

Look at how much architecture is visible.

Launch is suitable for movement across concurrency boundaries.

LaunchCache owns mutable cached state.

LaunchModel owns UI-facing state on MainActor.

refresh() is asynchronous.

The network operation can suspend.

Cancellation is acknowledged.

The cache update crosses into another actor's isolation.

The final UI mutation remains MainActor-isolated.

This is the kind of architecture Swift Concurrency makes possible without requiring the feature to be designed primarily as a set of queues and thread transitions.

33 · TUTORIAL

Benefit: Migration Can Be Incremental

Commercial applications are rarely rewritten from zero.

Most iOS developers are working inside products that already contain years of callbacks, delegates, GCD queues, operation queues and third-party libraries.

Swift Concurrency does not require every historical API to disappear on day one.

Async code can coexist with existing APIs, and continuations provide mechanisms for bridging callback-based operations into async functions where appropriate.

This makes adoption a migration problem rather than necessarily a complete rewrite.

34 · TUTORIAL

Benefit: Concurrency Knowledge Becomes Transferable Across Features

Once your application uses common Swift Concurrency concepts consistently, developers do not need to relearn an entirely custom synchronization system for every feature.

The same vocabulary appears throughout the project:

CODE EXAMPLE

Task
async
await
actor
@MainActor
Sendable
structured concurrency
cancellation
isolation
executor

A developer who understands those contracts can move between networking, persistence, UI state and background processing with a common mental model.

This reduces architectural noise.

35 · TUTORIAL

Benefit: Concurrency Becomes Easier to Teach

This may sound like a softer benefit, but it matters enormously on professional teams.

Compare teaching a new developer:

CODE EXAMPLE

This class is mostly accessed
from queue A.

Except this callback comes
from queue B.

Then we bounce onto main.

But this property has its own lock.

Do not call method C from queue D.

with:

CODE EXAMPLE

This model is @MainActor.

This cache is an actor.

These values are Sendable.

This async function can suspend.

These child Tasks belong
to this parent operation.

The second architecture still requires knowledge.

But the knowledge is based on shared Swift language concepts rather than an undocumented concurrency system invented by one code base.

36 · TUTORIAL

Benefit: Concurrency Rules Become Searchable and Reviewable

This sounds trivial until you work on an application with millions of lines of code.

You can search for:

CODE EXAMPLE

@MainActor

actor

Sendable

Task {

Task.detached

async let

withTaskGroup

and discover meaningful pieces of the application's concurrency architecture.

That is much harder when concurrency ownership is encoded only through arbitrary queue variable names and unwritten team conventions.

37 · TUTORIAL

Benefit: Swift Concurrency Creates a Common Architecture Across Apple Platforms

Swift Concurrency is a language-level model rather than an iPhone-only concurrency trick.

The same core concepts can be used across Swift applications and Apple platforms:

CODE EXAMPLE

Tasks

async / await

Actors

MainActor

Sendable

Structured concurrency

Cancellation

This gives developers a more consistent concurrency vocabulary as applications expand across iPhone, iPad, Mac, Apple Watch, Apple TV, visionOS and server-side Swift environments.

38 · TUTORIAL

Benefit: The Runtime Can Manage Execution Better Than Individual Features

One application feature rarely has enough information to know the ideal number of threads for the entire device.

A developer working on the image loader should not need to decide:

CODE EXAMPLE

This feature needs exactly 14 threads.

while another developer independently decides:

CODE EXAMPLE

The database needs 12 more.

and another framework creates another pool.

Swift's concurrency runtime and the operating system can coordinate execution resources at levels with much broader knowledge of available work and hardware.

The application can describe the work instead of every feature attempting to become its own scheduler.

39 · TUTORIAL

Benefit: We Can Separate Safety From Performance

This is another major conceptual improvement.

Historically, developers can easily combine two different questions:

CODE EXAMPLE

Is this state safe?

and

Where should this expensive
calculation execute?

Swift Concurrency lets us reason about them separately.

Actor isolation can answer:

CODE EXAMPLE

Who safely owns this state?

while execution architecture answers:

CODE EXAMPLE

Where should this CPU-heavy work run
so it does not delay MainActor?

An actor does not make expensive work inexpensive.

MainActor does not make code fast.

Sendable does not make something asynchronous.

Separating these contracts gives us a much cleaner architecture.

40 · TUTORIAL

Benefit: Modern Swift Can Make Unsafe Assumptions Visible

Moving an existing code base toward strict concurrency often initially produces compiler complaints.

That can feel frustrating.

But many of those diagnostics reveal assumptions that previously existed without being formally checked.

For example:

CODE EXAMPLE

This mutable reference type
is being transferred between
concurrency domains.

Is that actually safe?

or:

CODE EXAMPLE

This property belongs to MainActor.

Why is this nonisolated code
trying to access it directly?

The compiler is forcing the architecture to answer questions that the old code may simply have ignored.

41 · TUTORIAL

Benefit: Fewer Timing Bugs Have to Be Discovered by Users

This is ultimately where concurrency safety matters.

A data race is not an academic inconvenience.

It can produce:

CODE EXAMPLE

lost state

incorrect account information

duplicated updates

missing values

crashes

corrupted caches

inconsistent UI state

rare production-only failures

When Swift can make more concurrency mistakes impossible or diagnosable before release, fewer bugs have to be discovered after a customer happens to trigger exactly the wrong execution timing.

That is a direct product-quality benefit.

42 · TUTORIAL

Benefit: Concurrency Becomes Part of API Design

Look at this API:

CODE EXAMPLE

@MainActor
protocol LaunchPresenting {

    func refresh() async throws

}

The API communicates far more than a callback-based signature that hides its execution contract.

It communicates isolation.

It communicates asynchrony.

It communicates error behaviour.

Concurrency decisions become part of how APIs are designed rather than being an invisible implementation detail discovered only after reading the source.

43 · TUTORIAL

Benefit: Swift Concurrency Gives Us One Mental Model

This might be the largest benefit of all.

Without a coherent model, iOS concurrency can feel like dozens of unrelated rules:

CODE EXAMPLE

Do not block the main thread.

Use a background queue.

Task is not a thread.

await does not mean background.

Actors serialize state.

MainActor owns UI work.

Sendable crosses boundaries.

Task groups create child Tasks.

Cancellation is cooperative.

But once Swift Concurrency is understood as one system, these statements connect.

EXECUTION DIAGRAM

APPLICATION WORK
       │
       ▼
     TASKS
       │
       ▼
  async / await
       │
       ├──── suspension
       │
       ├──── cancellation
       │
       ├──── child Tasks
       │
       └──── errors
       │
       ▼
   ISOLATION
       │
       ├──── actors
       ├──── MainActor
       └──── Sendable
       │
       ▼
    EXECUTORS
       │
       ▼
SYSTEM THREADS
       │
       ▼
OS SCHEDULER
       │
       ▼
   CPU CORES

Now we have one architecture rather than a collection of warnings.

44 · TUTORIAL

What Swift Concurrency Does Not Promise

A good list of benefits also needs boundaries.

Swift Concurrency does not make every operation faster.

It does not make every operation parallel.

It does not automatically move CPU-heavy code away from MainActor.

It does not automatically divide long synchronous loops into tiny cooperative pieces.

It does not make every logical race condition impossible.

It does not remove operating-system threads.

It does not mean developers can stop understanding execution, ownership and architecture.

Swift Concurrency gives us better tools and stronger language guarantees.

We still have to design the application correctly.

45 · TUTORIAL

The Real Benefit Is Cooperative Architecture

Return to the model we have been developing throughout this documentation.

An application contains many things that need to make progress:

CODE EXAMPLE

UI rendering

User input

Network requests

Image loading

Database work

Caching

Animations

Account refresh

Search

Background computation

The goal should not be for one feature to acquire an execution resource and then prevent everything else from progressing until it has completed all possible work.

A better architecture asks:

💡 How Can Everything Make Progress?

Which work can execute concurrently?

Which work must remain sequential?

Where can Tasks suspend?

Which mutable state must be isolated?

Which values may cross those boundaries?

Which work should be cancelled when it is no longer useful?

This is the mindset behind modern cooperative concurrency.

46 · TUTORIAL

A Useful Before and After

We can summarise the transition like this:

CODE EXAMPLE

BEFORE

Threads
Queues
Locks
Callbacks
Dispatch groups
Manual state synchronization
Team conventions
Timing assumptions


SWIFT CONCURRENCY

Tasks
async / await
Suspension
Structured child Tasks
Cancellation
Actors
MainActor
Sendable
Compiler-checked isolation
Executors

The old mechanisms still exist beneath and alongside the new system.

Swift Concurrency does not erase computing history.

It gives application developers a more useful abstraction above it.

47 · TUTORIAL

What to Remember

💡 What to Remember

Swift Concurrency gives Swift a structured, language-level model for asynchronous and concurrent application work.

Tasks let us represent asynchronous work without requiring one dedicated system thread per operation.

async and await allow asynchronous code to read much more like ordinary sequential Swift.

A Task can suspend while waiting rather than requiring a thread to remain blocked for the entire asynchronous lifetime of the operation.

This helps interactive applications remain responsive while other work continues making progress.

Actors give mutable state explicit isolation and serialize actor-isolated execution.

MainActor gives UI-facing mutable state a clear main execution owner.

Sendable gives Swift information about values that can safely cross concurrency domains.

Strict concurrency checking allows many isolation and data-race risks to become compiler diagnostics instead of intermittent runtime failures.

Structured concurrency gives asynchronous child work meaningful parent-child lifetimes.

async let provides a direct way to express a fixed amount of independent concurrent work.

Task groups provide structure for a dynamic number of child Tasks.

Task cancellation lets work cooperate when its result is no longer needed.

Errors fit naturally into async functions through Swift's existing throws, try and catch model.

Swift Concurrency reduces the need for individual features to manually manage threads, queues and synchronization mechanisms.

Concurrency intent becomes visible in function signatures, type declarations and isolation annotations.

The runtime can coordinate many Tasks over a smaller collection of underlying execution resources.

Independent work can overlap and may use multiple CPU cores when parallel execution is available and appropriate.

Concurrency does not guarantee faster execution, and it does not automatically make expensive synchronous work cooperative.

The biggest architectural change is that we can increasingly describe work, ownership, isolation, lifetime and dependencies rather than manually describing threads.

48 · TUTORIAL

Your Next Move

The benefits of Swift Concurrency become much easier to appreciate once we stop treating it as a collection of new keywords.

async is useful because functions can participate in asynchronous execution.

await is useful because suspension points become explicit.

Tasks are useful because asynchronous work no longer needs to be represented as a dedicated thread.

Actors are useful because mutable state can have an owner.

MainActor is useful because UI-facing state can have a clear isolation domain.

Sendable is useful because values crossing concurrency boundaries can be checked.

Structured concurrency is useful because asynchronous work has relationships and lifetimes.

And the Swift Concurrency Runtime ties those concepts together into one execution model.

That means the next step is not simply to memorise more syntax.

The next step is to learn each of those parts well enough that you can look at an existing iOS application and answer:

How should this application be structured so that all of its important work can make progress safely and cooperatively?

That is the journey through Swift Concurrency.

bottom of page