# How to Implement Sendable Conformance for Custom Types in Swift: 7 Essential Steps

> Learn to implement Sendable conformance for custom Swift types in 7 steps. Ensure thread safety for your code and prevent data races efficiently.

- Repository: [Thomas Ricouard/Skills](https://github.com/Dimillian/Skills)
- Tags: how-to-guide
- Published: 2026-04-01

---

**To implement Sendable conformance for custom types in Swift, ensure all stored properties are themselves Sendable for immutable value types, apply `@unchecked Sendable` only when manually guaranteeing thread-safety for reference types, and isolate non-Sendable members using `@MainActor` or thread-safe containers.**

Swift 6 (and Swift 5.7 with concurrency warnings enabled) enforces strict data-race safety through the `Sendable` protocol, which marks types safe to transfer across actor boundaries. The Dimillian/Skills repository documents these patterns extensively in its Swift Concurrency Expert skill, providing practical guidance for safely adopting strict concurrency checking in modern Swift code.

## What is Sendable and When Do You Need It?

`Sendable` is a marker protocol introduced in Swift 6 (back-ported to Swift 5.7) that tells the compiler a type can safely cross concurrency domains—such as being passed to `Task.detached`, stored in actor-isolated properties, or captured by `@Sendable` closures—without causing data races.

According to [`swift-concurrency-expert/SKILL.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/SKILL.md), you must implement Sendable conformance whenever your custom type moves between isolation boundaries, including closure arguments, async function parameters, and actor-level properties. The compiler verifies this by checking that every stored property is itself `Sendable`, or that you explicitly assert safety with `@unchecked Sendable`.

## Step-by-Step Implementation Guide

The following seven-step process, derived from the concurrency documentation in the Dimillian/Skills repository, ensures your types meet Swift's strict concurrency requirements.

### 1. Identify Concurrency Boundaries

First, locate where your type crosses actor or task boundaries. As noted in [`swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md), these boundaries occur when passing values to `Task.detached`, storing properties in actors, or capturing variables in `@Sendable` closures. These are the exact locations where the compiler validates `Sendable` conformance.

### 2. Prefer Immutable Value Types

Declare all stored properties as `let` constants to leverage Swift's automatic `Sendable` inference. Immutable value types are inherently thread-safe because they cannot be mutated after creation, making them automatically `Sendable` when composed of `Sendable` members.

### 3. Verify Sendable Members

Ensure every property type already conforms to `Sendable`. Standard library types like `String`, `Int`, and `Array` where `Element: Sendable` already conform. For custom property types, you must verify they are also `Sendable` compliant before the compiler will accept your conformance.

### 4. Add Explicit Conformance

When your type consists only of `Sendable` properties, declare explicit conformance:

```swift
struct Point: Sendable {
    let x: Double
    let y: Double
}

```

The compiler automatically accepts this conformance because `Double` is `Sendable` and the struct is immutable.

### 5. Resolve Non-Sendable Properties

For properties that are not `Sendable`—such as mutable collections or reference-type objects—choose one of these approaches:

- **Replace with Sendable alternatives**: Convert `NSMutableArray` to `[Element]` where `Element: Sendable`
- **Apply actor isolation**: Wrap the non-Sendable property in a `@MainActor` container as shown in [`swift-concurrency-expert/references/approachable-concurrency.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/references/approachable-concurrency.md)

### 6. Use @unchecked Sendable as Last Resort

For reference types where you can manually prove thread-safety (such as immutable objects never mutated after creation), use `@unchecked Sendable`:

```swift
final class Counter: @unchecked Sendable {
    var value: Int = 0
}

```

Only apply this when you can guarantee the instance will never be mutated from multiple threads simultaneously. The file [`swift-concurrency-expert/references/swift-6-2-concurrency.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/references/swift-6-2-concurrency.md) demonstrates how improper use leads to compile-time errors.

### 7. Verify with Compiler Flags

Build with `-warn-concurrency` to catch hidden races. This flag surfaces violations that might pass initial compilation but create runtime data races, ensuring your implementation is complete.

## Practical Implementation Patterns

The Dimillian/Skills repository provides concrete patterns for common scenarios encountered when implementing `Sendable` conformance.

### Automatic Conformance for Structs

Immutable structs composed of `Sendable` types receive automatic compiler validation:

```swift
// ✅ Simple immutable struct – automatically Sendable
struct Point: Sendable {
    let x: Double
    let y: Double
}

```

### Isolating Reference Types

When you cannot make a class truly immutable, isolate it to the Main Actor:

```swift
// ✅ Use a Sendable wrapper around a non‑Sendable reference
struct CounterBox: Sendable {
    @MainActor let counter: Counter
}

```

This pattern from [`swift-concurrency-expert/references/approachable-concurrency.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/references/approachable-concurrency.md) allows the struct to be `Sendable` while the wrapped reference remains confined to the main actor.

### Crossing Concurrency Boundaries

When passing custom types to detached tasks, the compiler enforces `Sendable`:

```swift
func fetchData<Model: Sendable>(_ model: Model) async {
    Task.detached {
        // `model` is safe to use here because it is Sendable
        await process(model)
    }
}

```

Similarly, when using `withTaskGroup`, all captured values must conform:

```swift
func loadImages(urls: [URL]) async {
    await withTaskGroup(of: Void.self) { group in
        for url in urls {
            group.addTask {
                // `url` is a Sendable value type
                let data = try await fetchImage(at: url)
                await MainActor.run {
                    imageView.image = UIImage(data: data)
                }
            }
        }
    }
}

```

## Summary

- **Immutable value types** with `Sendable` properties automatically satisfy `Sendable` conformance without special handling
- **Reference types** require `@unchecked Sendable` only when you can manually guarantee thread-safety, or must be isolated to actors using `@MainActor`
- **Non-Sendable properties** should be replaced with `Sendable` alternatives or wrapped in thread-safe containers
- **Compiler verification** using `-warn-concurrency` catches hidden data races before runtime
- The Dimillian/Skills repository documents these patterns in [`swift-concurrency-expert/SKILL.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/SKILL.md) and related reference files to help implement strict concurrency checking correctly

## Frequently Asked Questions

### What is the difference between Sendable and @unchecked Sendable?

`Sendable` requires the compiler to verify that all stored properties are themselves `Sendable`, ensuring type safety through static analysis. `@unchecked Sendable` bypasses these compiler checks, placing the responsibility on the developer to guarantee thread-safety manually. According to [`swift-concurrency-expert/references/swift-6-2-concurrency.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/references/swift-6-2-concurrency.md), you should only use `@unchecked` when you can prove the type is never mutated across concurrency domains, such as immutable reference types.

### When does Swift automatically infer Sendable conformance?

The compiler automatically infers `Sendable` conformance for **public** structs and enums when all stored properties are `Sendable` and the type is declared in the same module where conformance is needed. For **internal** and **private** types, Swift automatically synthesizes conformance when all members are `Sendable`. However, you must explicitly declare `Sendable` conformance for public types or when crossing module boundaries to ensure ABI stability.

### How do I handle non-Sendable properties in a custom type?

When a property type cannot conform to `Sendable` (such as `NSLock` or mutable Objective-C classes), you have three options: replace the property with a `Sendable` alternative (e.g., `Actor` instead of `NSLock`), wrap the property in an actor-isolated container using `@MainActor`, or refactor your architecture to keep the non-Sendable property within a single concurrency domain. The [`swift-concurrency-expert/references/approachable-concurrency.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/references/approachable-concurrency.md) file recommends preferring value types and actor isolation over `@unchecked Sendable` for these cases.

### Does Swift 6 require Sendable for all concurrency boundaries?

Yes, Swift 6 enforces `Sendable` requirements at all concurrency boundaries, including passing values to `Task.detached`, capturing values in `@Sendable` closures, and storing properties in actors. The [`swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md`](https://github.com/Dimillian/Skills/blob/main/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md) documentation explains that SwiftUI heavily utilizes these patterns, making `Sendable` conformance essential for modern Swift development. Build with `-warn-concurrency` in Swift 5.7+ to prepare for these requirements.