Sendable is a Swift protocol that marks types safe for passing between threads. When working with concurrency (actor, async/await, Task), the Swift compiler requires that all data passed between isolated contexts conform to Sendable. This prevents accidental passing of unsafe types that could lead to data races. The protocol acts as a contract: a type conforming to Sendable guarantees the absence of internal unsynchronized state. According to WWDC 2021, the Sendable protocol is a mandatory element when designing a safe multithreaded architecture.
Key Takeaways
Sendable is a protocol from the Swift standard library (SE-0302) that marks types safe for passing between isolation domains. An isolation domain means an actor, a Task, or a @MainActor context. Sendable contains no requirements — it acts as a safety marker.
Before Swift 5.5, developers could pass any object between threads via DispatchQueue, and the compiler did not check whether it was safe. Sendable closes this gap: now the compiler itself tracks cross-thread transfers and blocks unsafe ones. This makes concurrent code more reliable even before it runs.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
In the example, UserProfile can be safely passed between threads because it is a struct with constant properties of Sendable types. NonSendableClass will cause a compilation error when trying to pass it via await or into a Task.
Sendable types in Swift fall into three categories: value types with Sendable properties, final classes with immutable state, and functions/closures marked @Sendable. The compiler automatically infers Sendable conformance for value types.
Struct, enum and tuple automatically become Sendable if all their properties and associated values are also Sendable. This is a conservative approach: if at least one property does not conform to Sendable, the entire type will not be recognized as safe.
| Type | Sendable | Condition |
|---|---|---|
| Struct | Yes (without explicit declaration) | All properties are Sendable |
| Enum | Yes (without explicit declaration) | All associated values are Sendable |
| Final class | Yes (with explicit declaration) | All let properties are Sendable, no var |
| Non-final class | No | Cannot be Sendable due to inheritance |
All built-in Swift types — Int, String, Double, Bool, Optional, Array, Dictionary, Set — conform to Sendable. This makes type composition safe by default. The developer only needs to watch custom classes.
@unchecked Sendable is a mechanism that allows a class to explicitly declare itself as Sendable, bypassing compiler checks. The developer takes responsibility for the thread safety of such a class. This is useful for Objective-C bridges and optimized structures.
@unchecked Sendable is used when a class internally guarantees safety through locks or atomic operations, but the compiler cannot verify this statically. For example, a class with os_unfair_lock or pthread_mutex_t — its thread safety is ensured by code, but Swift does not see it.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
Using @unchecked Sendable should be done carefully. It is an explicit signal to other developers: “I checked, this is safe.” An implementation error can lead to hard-to-find races. Before using @unchecked, make sure the type really cannot be rewritten as a value type.
Actor and Sendable are two sides of the same coin. Actor isolates its state, but to exchange data with the outside world, it must return Sendable types. If an actor method returns a non-Sendable type, the compiler gives a warning or error.
When an actor sends data to external code, this data crosses the isolation boundary. Sendable guarantees that the recipient can safely use it outside the actor. The actor itself remains isolated — its internal state is not exposed.
struct AccountSnapshot: Sendable {
let id: UUID
let balance: Double
let lastUpdated: Date
}
actor BankActor {
private var balance: Double = 0
func snapshot() async -> AccountSnapshot {
return AccountSnapshot(
id: UUID(),
balance: balance,
lastUpdated: Date()
)
}
}
AccountSnapshot is a Sendable struct that contains only let properties of Sendable types. This approach is a best practice for extracting data from an actor. The state snapshot is passed by value, and the actor does not lose control over its state.
@Sendable is an attribute for functions and closures that guarantees the closure does not capture non-Sendable data in a mutable way. When a closure is passed to a Task or actor method, it must be Sendable.
The compiler checks that the @Sendable closure does not capture mutable references to classes. Capturing let properties of Sendable types is allowed. Capturing a var variable of a reference type will cause an error because the closure could be executed simultaneously with mutation.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — capturing var mutates counter, compile error
performAsync { // ❌ Mutation of captured var
print(constant)
}
The rule is simple: a @Sendable closure can only capture data that is itself Sendable and will not be mutated from outside. For classes, capturing a weak reference weak self is allowed if the class is not marked as Sendable. This prevents classic retain cycles and race conditions.
Frequently Asked Questions
Sendable is a marker that tells the compiler: “this type can be safely passed between threads.” Structs and constants are usually Sendable by default, classes are not unless explicitly stated.
Value types (struct, enum) with Sendable properties, final classes with immutable state, and all basic Swift types: Int, String, Double, Bool, Array, Dictionary, Optional — conform to Sendable automatically.
@unchecked Sendable is a way to declare a class as Sendable without static compiler checks. The developer guarantees safety on their own, often through locks or atomic operations.
Actor isolates its state, but when returning data to external code, this data crosses the isolation boundary. Sendable guarantees that the recipient will not encounter races when using this data outside the actor.
Add the Sendable protocol to a final class, all of whose properties are constants of Sendable types. If the class uses locks, you can use @unchecked Sendable, but this requires caution.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also