Sendable è un protocollo Swift che contrassegna i tipi sicuri per il trasferimento tra thread. Quando si lavora con la concorrenza (actor, async/await, Task), il compilatore Swift richiede che tutti i dati trasferiti tra contesti isolati siano conformi a Sendable. Ciò impedisce il trasferimento accidentale di tipi non sicuri che potrebbero portare a race condition. Il protocollo funge da contratto: un tipo conforme a Sendable garantisce l'assenza di stato interno non sincronizzato. Secondo WWDC 2021, il protocollo Sendable è un elemento obbligatorio nella progettazione di un'architettura multithread sicura.
Punti chiave
Sendable è un protocollo della libreria standard Swift (SE-0302) che contrassegna i tipi sicuri per il trasferimento tra domini di isolamento. Un dominio di isolamento significa un actor, un'attività Task o un contesto @MainActor. Sendable non contiene requisiti — funge da marcatore di sicurezza.
Prima di Swift 5.5, gli sviluppatori potevano trasferire qualsiasi oggetto tra thread tramite DispatchQueue e il compilatore non verificava se fosse sicuro. Sendable colma questa lacuna: ora il compilatore stesso traccia i trasferimenti tra thread e blocca quelli non sicuri. Ciò rende il codice concorrente più affidabile ancora prima dell'esecuzione.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
Nell'esempio, UserProfile può essere trasferito in sicurezza tra thread perché è una struct con proprietà costanti di tipi Sendable. NonSendableClass causerà un errore di compilazione quando si tenta di trasferirlo tramite await o in una Task.
I tipi Sendable in Swift si dividono in tre categorie: tipi valore con proprietà Sendable, classi finali con stato immutabile e funzioni/closure contrassegnate con @Sendable. Il compilatore deduce automaticamente la conformità Sendable per i tipi valore.
Struct, Enum e tupla diventano automaticamente Sendable se tutte le loro proprietà e valori associati sono anch'essi Sendable. Questo è un approccio conservativo: se almeno una proprietà non è conforme a Sendable, l'intero tipo non sarà riconosciuto come sicuro.
| Tipo | Sendable | Condizione |
|---|---|---|
| Struct | Sì (senza dichiarazione esplicita) | Tutte le proprietà sono Sendable |
| Enum | Sì (senza dichiarazione esplicita) | Tutti i valori associati sono Sendable |
| Final class | Sì (con dichiarazione esplicita) | Tutte le proprietà let sono Sendable, nessun var |
| Non-final class | No | Non può essere Sendable a causa dell'ereditarietà |
Tutti i tipi incorporati di Swift — Int, String, Double, Bool, Optional, Array, Dictionary, Set — sono conformi a Sendable. Ciò rende la composizione dei tipi sicura per impostazione predefinita. Lo sviluppatore deve solo prestare attenzione alle classi personalizzate.
@unchecked Sendable è un meccanismo che consente a una classe di dichiararsi esplicitamente come Sendable, bypassando i controlli del compilatore. Lo sviluppatore si assume la responsabilità della sicurezza dei thread di tale classe. Ciò è utile per i bridge Objective-C e le strutture ottimizzate.
@unchecked Sendable viene utilizzato quando una classe garantisce internamente la sicurezza tramite lock o operazioni atomiche, ma il compilatore non può verificarlo staticamente. Ad esempio, una classe con os_unfair_lock o pthread_mutex_t — la sua sicurezza dei thread è garantita dal codice, ma Swift non lo vede.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
L'uso di @unchecked Sendable deve essere fatto con cautela. È un segnale esplicito per altri sviluppatori: «ho verificato, è sicuro». Un errore nell'implementazione può portare a race condition difficili da individuare. Prima di usare @unchecked, assicurati che il tipo non possa davvero essere riscritto come tipo valore.
Actor e Sendable sono due facce della stessa medaglia. Actor isola il suo stato, ma per scambiare dati con il mondo esterno deve restituire tipi Sendable. Se un metodo di actor restituisce un tipo non Sendable, il compilatore emette un avviso o un errore.
Quando un actor invia dati a codice esterno, questi dati attraversano il confine di isolamento. Sendable garantisce che il destinatario possa usarli in sicurezza al di fuori dell'actor. L'actor rimane isolato — il suo stato interno non viene esposto.
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 è una struct Sendable che contiene solo proprietà let di tipi Sendable. Questo approccio è una best practice per estrarre dati da un actor. Lo snapshot dello stato viene trasferito per valore e l'actor non perde il controllo del suo stato.
@Sendable è un attributo per funzioni e closure che garantisce che la closure non catturi dati non Sendable in modo mutabile. Quando una closure viene trasferita a una Task o a un metodo di actor, deve essere Sendable.
Il compilatore verifica che la closure @Sendable non catturi riferimenti mutabili a classi. La cattura di proprietà let di tipi Sendable è consentita. La cattura di una variabile var di un tipo riferimento causerà un errore perché la closure potrebbe essere eseguita simultaneamente alla mutazione.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — catturare var muta counter, errore di compilazione
performAsync { // ❌ Mutazione della variabile catturata
print(constant)
}
La regola è semplice: una closure @Sendable può catturare solo dati che sono essi stessi Sendable e che non verranno mutati dall'esterno. Per le classi, è consentita la cattura di un riferimento debole weak self se la classe non è contrassegnata come Sendable. Ciò previene i classici cicli di retain e le race condition.
Domande frequenti
Sendable è un marcatore che dice al compilatore: «questo tipo può essere trasferito in sicurezza tra thread». Le struct e le costanti sono solitamente Sendable per impostazione predefinita, le classi no, a meno che non sia specificato esplicitamente.
I tipi valore (struct, enum) con proprietà Sendable, le classi finali con stato immutabile e tutti i tipi di base Swift: Int, String, Double, Bool, Array, Dictionary, Optional — sono automaticamente conformi a Sendable.
@unchecked Sendable è un modo per dichiarare una classe come Sendable senza controllo statico del compilatore. Lo sviluppatore garantisce la sicurezza da solo, spesso tramite lock o operazioni atomiche.
Actor isola il suo stato, ma quando restituisce dati al codice esterno, questi dati attraversano il confine di isolamento. Sendable garantisce che il destinatario non incontri race condition nell'utilizzo di questi dati al di fuori dell'actor.
Aggiungi il protocollo Sendable a una classe finale, le cui proprietà siano tutte costanti di tipi Sendable. Se la classe usa lock, si può usare @unchecked Sendable, ma ciò richiede cautela.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche