Skip to main content
Swiftโ€™s structured concurrency provides many warnings for situations in which you might be using types and functions that are not thread-safe. In Swift 6, most of these warnings will become errors, so you need to know how to prove to the compiler that your types are safe to use concurrently.

Overview

The primary way to create an Effect in the library is via Effect.run, which takes a @Sendable, asynchronous closure. This restricts the types of closures you can use for your effects.
The closure can only capture Sendable variables that are bound with let. Mutable variables and non-Sendable types are not allowed.
There are two primary scenarios where youโ€™ll encounter this restriction:
  1. Accessing state from within an effect
  2. Accessing a dependency from within an effect

Accessing state in an effect

Reducers execute with a mutable, inout state variable, which cannot be accessed from @Sendable closures.

The problem

The solution

Explicitly capture the state as an immutable value:
Capturing just the values you need makes the closure more focused and explicit about its dependencies.

Accessing dependencies in an effect

Dependencies can be used from asynchronous and concurrent contexts, so they must be Sendable.

Registering dependencies

When extending DependencyValues, youโ€™ll get warnings if your dependency isnโ€™t Sendable:

Making dependencies Sendable

To fix this, make sure the interface type only holds onto Sendable data, and annotate closure-based endpoints as @Sendable:
1

Add @Sendable to closures

2

Conform to Sendable

If your dependency only contains Sendable properties and @Sendable closures, Swift will automatically infer Sendable conformance. Otherwise, explicitly conform:
Any closures you use to construct FactClient values must now be @Sendable, which restricts what they can capture.

Common patterns

Passing state to async work

Using dependencies in effects

Shared state in effects

Shared state is already Sendable, so you can capture it directly:

Actor isolation

Reducers in TCA are @MainActor isolated by default, which means:

State access is safe

All state mutations happen on the main actor

Effects run elsewhere

.run effects execute in the cooperative thread pool

Send is main actor

Sending actions back always happens on the main actor

Views are synchronized

SwiftUI views observe state on the main actor

Crossing actor boundaries

When effects need to send actions, they automatically hop to the main actor:

Testing concurrent code

When testing features with concurrency:
1

Use controlled clocks

2

Assert on received actions

3

Ensure effects complete

The test store automatically ensures all effects finish before the test ends.

Swift 6 considerations

In Swift 6, concurrency warnings become errors. To prepare:
In your package or Xcode project, enable complete concurrency checking to get warnings now:
Ensure all dependencies conform to Sendable and use @Sendable closures.
Always use capture lists when accessing state in effects:
Donโ€™t access global mutable state. Wrap it in dependencies.

Common errors and fixes

Best practices

Always capture state explicitly

Use capture lists even when it seems unnecessary

Make dependencies Sendable

Annotate all closures with @Sendable

Avoid global state

Wrap globals in dependencies for testability

Use actor isolation wisely

Let TCA handle main actor isolation automatically

Resources

Swift Concurrency

Official Swift concurrency documentation

Dependencies

Learn more about dependency injection in TCA

Testing

Learn how to test concurrent features

Effects

Deep dive into the Effect type