Sendable est un protocole Swift qui marque les types comme sûrs pour le transfert entre threads. Lorsqu'on travaille avec la concurrence (actor, async/await, Task), le compilateur Swift exige que toutes les données transférées entre contextes isolés soient conformes à Sendable. Cela évite le transfert accidentel de types non sûrs qui pourraient entraîner des compétitions de données. Le protocole agit comme un contrat : un type conforme à Sendable garantit l'absence d'état interne non synchronisé. Selon WWDC 2021, le protocole Sendable est un élément obligatoire lors de la conception d'une architecture multithread sécurisée.
Points clés
Sendable est un protocole de la bibliothèque standard Swift (SE-0302) qui marque les types comme sûrs pour le transfert entre domaines d'isolation. Un domaine d'isolation désigne un actor, une tâche Task ou un contexte @MainActor. Sendable ne contient aucune exigence — il agit comme un marqueur de sécurité.
Avant Swift 5.5, les développeurs pouvaient transférer n'importe quel objet entre threads via DispatchQueue, et le compilateur ne vérifiait pas si c'était sûr. Sendable comble cette lacune : désormais, le compilateur lui-même suit les transferts entre threads et bloque ceux qui sont dangereux. Cela rend le code concurrent plus fiable avant même son exécution.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
Dans l'exemple, UserProfile peut être transféré en toute sécurité entre threads car c'est une structure avec des propriétés constantes de types Sendable. NonSendableClass provoquera une erreur de compilation lors d'une tentative de transfert via await ou vers une Task.
Les types Sendable en Swift se divisent en trois catégories : les types valeur avec propriétés Sendable, les classes finales avec état immuable et les fonctions/closures marquées @Sendable. Le compilateur déduit automatiquement la conformité Sendable pour les types valeur.
Struct, Enum et tuple deviennent automatiquement Sendable si toutes leurs propriétés et valeurs associées sont également Sendable. C'est une approche conservatrice : si au moins une propriété n'est pas conforme à Sendable, le type entier ne sera pas reconnu comme sûr.
| Type | Sendable | Condition |
|---|---|---|
| Struct | Oui (sans déclaration explicite) | Toutes les propriétés sont Sendable |
| Enum | Oui (sans déclaration explicite) | Toutes les valeurs associées sont Sendable |
| Final class | Oui (avec déclaration explicite) | Toutes les propriétés let sont Sendable, pas de var |
| Non-final class | Non | Ne peut pas être Sendable à cause de l'héritage |
Tous les types intégrés Swift — Int, String, Double, Bool, Optional, Array, Dictionary, Set — sont conformes à Sendable. Cela rend la composition de types sûre par défaut. Le développeur n'a qu'à surveiller les classes personnalisées.
@unchecked Sendable est un mécanisme qui permet à une classe de se déclarer explicitement comme Sendable, en contournant les vérifications du compilateur. Le développeur assume la responsabilité de la sécurité des threads de cette classe. C'est utile pour les ponts Objective-C et les structures optimisées.
@unchecked Sendable est utilisé lorsqu'une classe garantit internement la sécurité par des verrous ou des opérations atomiques, mais le compilateur ne peut pas le vérifier statiquement. Par exemple, une classe avec os_unfair_lock ou pthread_mutex_t — sa sécurité des threads est assurée par le code, mais Swift ne le voit pas.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
L'utilisation de @unchecked Sendable doit être faite avec prudence. C'est un signal explicite aux autres développeurs : « J'ai vérifié, c'est sûr. » Une erreur d'implémentation peut entraîner des compétitions difficiles à détecter. Avant d'utiliser @unchecked, assurez-vous que le type ne peut vraiment pas être réécrit comme un type valeur.
Actor et Sendable sont les deux faces d'une même pièce. Actor isole son état, mais pour échanger des données avec le monde extérieur, il doit retourner des types Sendable. Si une méthode d'actor retourne un type non Sendable, le compilateur émet un avertissement ou une erreur.
Lorsqu'un actor envoie des données à du code externe, ces données traversent la limite d'isolation. Sendable garantit que le destinataire peut les utiliser en toute sécurité en dehors de l'actor. L'actor reste isolé — son état interne n'est pas exposé.
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 est une structure Sendable qui contient uniquement des propriétés let de types Sendable. Cette approche est une bonne pratique pour extraire des données d'un actor. L'instantané d'état est transmis par valeur, et l'actor ne perd pas le contrôle de son état.
@Sendable est un attribut pour les fonctions et closures qui garantit que la closure ne capture pas de données non Sendable de manière mutable. Lorsqu'une closure est transférée à une Task ou à une méthode d'actor, elle doit être Sendable.
Le compilateur vérifie que la closure @Sendable ne capture pas de références mutables vers des classes. La capture de propriétés let de types Sendable est autorisée. La capture d'une variable var d'un type référence entraînera une erreur car la closure pourrait être exécutée simultanément avec la mutation.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — capturer var mute counter, erreur de compilation
performAsync { // ❌ Mutation de la variable capturée
print(constant)
}
La règle est simple : une closure @Sendable ne peut capturer que les données qui sont elles-mêmes Sendable et qui ne seront pas mutées de l'extérieur. Pour les classes, la capture d'une référence faible weak self est autorisée si la classe n'est pas marquée comme Sendable. Cela évite les cycles de rétention classiques et les compétitions de données.
Questions fréquentes
Sendable est un marqueur qui indique au compilateur : « ce type peut être transféré en toute sécurité entre threads ». Les structures et constantes sont généralement Sendable par défaut, les classes ne le sont pas sauf indication explicite.
Les types valeur (struct, enum) avec propriétés Sendable, les classes finales avec état immuable, et tous les types de base Swift : Int, String, Double, Bool, Array, Dictionary, Optional — sont automatiquement conformes à Sendable.
@unchecked Sendable est un moyen de déclarer une classe comme Sendable sans vérification statique du compilateur. Le développeur garantit la sécurité lui-même, souvent par des verrous ou des opérations atomiques.
Actor isole son état, mais lors du retour de données au code externe, ces données traversent la limite d'isolation. Sendable garantit que le destinataire ne rencontrera pas de compétitions en utilisant ces données en dehors de l'actor.
Ajoutez le protocole Sendable à une classe finale dont toutes les propriétés sont des constantes de types Sendable. Si la classe utilise des verrous, @unchecked Sendable peut être utilisé, mais cela nécessite de la prudence.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi