Sendable ist ein Swift-Protokoll, das Typen als sicher für die Übergabe zwischen Threads markiert. Bei der Arbeit mit Nebenläufigkeit (actor, async/await, Task) verlangt der Swift-Compiler, dass alle zwischen isolierten Kontexten übergebenen Daten Sendable entsprechen. Dies verhindert die versehentliche Übergabe unsicherer Typen, die zu Datenkonflikten führen können. Das Protokoll fungiert als Vertrag: Ein Typ, der Sendable entspricht, garantiert das Fehlen eines internen nicht synchronisierten Zustands. Laut WWDC 2021 ist das Sendable-Protokoll ein obligatorisches Element beim Entwurf einer sicheren Multithread-Architektur.
Wichtige Erkenntnisse
Sendable ist ein Protokoll der Swift-Standardbibliothek (SE-0302), das Typen als sicher für die Übergabe zwischen Isolationsdomänen markiert. Eine Isolationsdomäne bedeutet einen Actor, eine Task oder einen @MainActor-Kontext. Sendable enthält keine Anforderungen — es fungiert als Sicherheitsmarker.
Vor Swift 5.5 konnten Entwickler jedes Objekt über DispatchQueue zwischen Threads übergeben, und der Compiler prüfte nicht, ob dies sicher war. Sendable schließt diese Lücke: Jetzt verfolgt der Compiler selbst Thread-übergreifende Übergaben und blockiert unsichere. Dies macht nebenläufigen Code bereits vor der Ausführung zuverlässiger.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
Im Beispiel kann UserProfile sicher zwischen Threads übergeben werden, da es eine Struktur mit konstanten Eigenschaften von Sendable-Typen ist. NonSendableClass verursacht einen Kompilierungsfehler beim Versuch, es über await oder an eine Task zu übergeben.
Sendable-Typen in Swift fallen in drei Kategorien: Wertetypen mit Sendable-Eigenschaften, finale Klassen mit unveränderlichem Zustand und mit @Sendable markierte Funktionen/Closures. Der Compiler leitet die Sendable-Konformität für Wertetypen automatisch ab.
Struct, Enum und Tuple werden automatisch Sendable, wenn alle ihre Eigenschaften und assoziierten Werte ebenfalls Sendable sind. Dies ist ein konservativer Ansatz: Wenn mindestens eine Eigenschaft nicht Sendable entspricht, wird der gesamte Typ nicht als sicher erkannt.
| Typ | Sendable | Bedingung |
|---|---|---|
| Struct | Ja (ohne explizite Deklaration) | Alle Eigenschaften sind Sendable |
| Enum | Ja (ohne explizite Deklaration) | Alle assoziierten Werte sind Sendable |
| Final class | Ja (mit expliziter Deklaration) | Alle let-Eigenschaften sind Sendable, kein var |
| Non-final class | Nein | Kann aufgrund von Vererbung nicht Sendable sein |
Alle integrierten Swift-Typen — Int, String, Double, Bool, Optional, Array, Dictionary, Set — entsprechen Sendable. Dies macht die Typkomposition standardmäßig sicher. Der Entwickler muss nur auf benutzerdefinierte Klassen achten.
@unchecked Sendable ist ein Mechanismus, der es einer Klasse erlaubt, sich selbst explizit als Sendable zu deklarieren und dabei Compiler-Prüfungen zu umgehen. Der Entwickler übernimmt die Verantwortung für die Threadsicherheit einer solchen Klasse. Dies ist nützlich für Objective-C-Brücken und optimierte Strukturen.
@unchecked Sendable wird verwendet, wenn eine Klasse intern durch Sperren oder atomare Operationen Sicherheit gewährleistet, der Compiler dies jedoch nicht statisch überprüfen kann. Zum Beispiel eine Klasse mit os_unfair_lock oder pthread_mutex_t — ihre Threadsicherheit wird durch Code sichergestellt, aber Swift sieht dies nicht.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
Die Verwendung von @unchecked Sendable sollte vorsichtig erfolgen. Es ist ein explizites Signal an andere Entwickler: „Ich habe es überprüft, es ist sicher.“ Ein Implementierungsfehler kann zu schwer auffindbaren Wettlaufsituationen führen. Bevor Sie @unchecked verwenden, stellen Sie sicher, dass der Typ wirklich nicht als Wertetyp umgeschrieben werden kann.
Actor und Sendable sind zwei Seiten derselben Medaille. Actor isoliert seinen Zustand, muss aber für den Datenaustausch mit der Außenwelt Sendable-Typen zurückgeben. Wenn eine Actor-Methode einen nicht-Sendable-Typ zurückgibt, gibt der Compiler eine Warnung oder einen Fehler aus.
Wenn ein Actor Daten an externen Code sendet, überschreiten diese Daten die Isolationsgrenze. Sendable garantiert, dass der Empfänger sie außerhalb des Actors sicher verwenden kann. Der Actor bleibt isoliert — sein interner Zustand wird nicht offengelegt.
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 ist eine Sendable-Struktur, die nur let-Eigenschaften von Sendable-Typen enthält. Dieser Ansatz ist eine bewährte Methode zum Extrahieren von Daten aus einem Actor. Der Zustandssnapshot wird als Wert übergeben, und der Actor verliert nicht die Kontrolle über seinen Zustand.
@Sendable ist ein Attribut für Funktionen und Closures, das garantiert, dass die Closure keine nicht-Sendable-Daten auf veränderliche Weise erfasst. Wenn eine Closure an eine Task oder Actor-Methode übergeben wird, muss sie Sendable sein.
Der Compiler prüft, dass die @Sendable-Closure keine veränderlichen Referenzen auf Klassen erfasst. Das Erfassen von let-Eigenschaften von Sendable-Typen ist erlaubt. Das Erfassen einer var-Variable eines Referenztyps führt zu einem Fehler, da die Closure gleichzeitig mit der Mutation ausgeführt werden könnte.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — Erfassen von var mutiert counter, Kompilierungsfehler
performAsync { // ❌ Mutation der erfassten Variable
print(constant)
}
Die Regel ist einfach: Eine @Sendable-Closure kann nur Daten erfassen, die selbst Sendable sind und von außen nicht mutiert werden. Bei Klassen ist das Erfassen einer schwachen Referenz weak self erlaubt, wenn die Klasse nicht als Sendable markiert ist. Dies verhindert klassische retain-Zyklen und Wettlaufsituationen.
Häufig gestellte Fragen
Sendable ist ein Marker, der dem Compiler sagt: „Dieser Typ kann sicher zwischen Threads übergeben werden.“ Strukturen und Konstanten sind normalerweise standardmäßig Sendable, Klassen nicht, sofern nicht explizit angegeben.
Wertetypen (struct, enum) mit Sendable-Eigenschaften, finale Klassen mit unveränderlichem Zustand und alle grundlegenden Swift-Typen: Int, String, Double, Bool, Array, Dictionary, Optional — entsprechen automatisch Sendable.
@unchecked Sendable ist eine Möglichkeit, eine Klasse ohne statische Compiler-Prüfung als Sendable zu deklarieren. Der Entwickler garantiert die Sicherheit selbst, oft durch Sperren oder atomare Operationen.
Actor isoliert seinen Zustand, aber bei der Rückgabe von Daten an externen Code überschreiten diese Daten die Isolationsgrenze. Sendable garantiert, dass der Empfänger bei der Verwendung dieser Daten außerhalb des Actors nicht auf Wettlaufsituationen stößt.
Fügen Sie das Sendable-Protokoll zu einer finalen Klasse hinzu, deren Eigenschaften allesamt Konstanten von Sendable-Typen sind. Wenn die Klasse Sperren verwendet, kann @unchecked Sendable verwendet werden, aber das erfordert Vorsicht.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch