Sendable — este un protocol Swift care marchează tipurile sigure pentru transmiterea între fire. În lucrul cu concurența (actor, async/await, Task), compilatorul Swift cere ca toate datele transmise între contextele izolate să fie conforme cu Sendable. Aceasta exclude transmiterea accidentală a tipurilor nesigure care pot duce la condiții de cursă. Protocolul acționează ca un contract: un tip conform cu Sendable garantează absența stării interne nesincronizate. Potrivit WWDC 2021, protocolul Sendable este un element obligatoriu în proiectarea arhitecturii sigure multi-thread.
Principalele
Sendable — este un protocol din biblioteca standard Swift (SE-0302) care marchează tipurile sigure pentru transmiterea între domenii de izolare. Prin domeniu de izolare se înțelege un actor, o sarcină Task sau un context @MainActor. Sendable nu conține cerințe — acționează ca un marker de siguranță.
Înainte de Swift 5.5, dezvoltatorii puteau transmite orice obiect între fire prin DispatchQueue, iar compilatorul nu verifica dacă este sigur. Sendable închide această breșă: acum compilatorul însuși urmărește transmiterile între fire și blochează pe cele nesigure. Acest lucru face codul concurent mai fiabil chiar înainte de execuție.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
În exemplu, UserProfile poate fi transmis între fire în siguranță, deoarece este o structură cu proprietăți constante de tipuri Sendable. NonSendableClass va cauza o eroare de compilare la încercarea de transmitere prin await sau în Task.
Tipurile Sendable în Swift se împart în trei categorii: tipuri valoare cu proprietăți Sendable, clase finale cu stare imutabilă și funcții/closure-uri marcate cu @Sendable. Compilatorul deduce automat conformitatea Sendable pentru tipurile valoare.
Struct, enum și tuple devin automat Sendable dacă toate proprietățile și valorile asociate sunt de asemenea Sendable. Aceasta este o abordare conservatoare: dacă măcar o proprietate nu este conformă cu Sendable, întregul tip nu va fi considerat sigur.
| Tip | Sendable | Condiție |
|---|---|---|
| Struct | Da (fără indicare explicită) | Toate proprietățile Sendable |
| Enum | Da (fără indicare explicită) | Toate valorile asociate Sendable |
| Final class | Da (cu indicare explicită) | Toate proprietățile let Sendable, fără var |
| Non-final class | Nu | Nu poate fi Sendable din cauza moștenirii |
Toate tipurile încorporate Swift — Int, String, Double, Bool, Optional, Array, Dictionary, Set — sunt conforme cu Sendable. Aceasta face compoziția tipurilor sigură implicit. Dezvoltatorul trebuie să fie atent doar la clasele personalizate.
@unchecked Sendable — este un mecanism care permite unei clase să se declare explicit ca Sendable, ocolind verificările compilatorului. Dezvoltatorul își asumă responsabilitatea pentru siguranța firelor unei astfel de clase. Este util pentru punțile Objective-C și structurile optimizate.
@unchecked Sendable se aplică atunci când clasa garantează intern siguranța prin blocaje sau operații atomice, dar compilatorul nu poate verifica acest lucru static. De exemplu, o clasă cu os_unfair_lock sau pthread_mutex_t — siguranța firelor este asigurată prin cod, dar Swift nu vede acest lucru.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
Utilizați @unchecked Sendable cu prudență. Acesta este un semnal clar pentru alți dezvoltatori: „am verificat, este sigur”. O eroare în implementare poate duce la condiții de cursă greu de depistat. Înainte de a aplica @unchecked, asigurați-vă că tipul nu poate fi rescris ca tip valoare.
Actor și Sendable sunt două fețe ale aceleiași medalii. Actor își izolează starea, dar pentru schimbul de date cu lumea exterioară trebuie să returneze tipuri Sendable. Dacă o metodă actor returnează un tip non-Sendable, compilatorul emite un avertisment sau o eroare.
Când actor trimite date codului extern, aceste date traversează granița de izolare. Sendable garantează că destinatarul le poate folosi în siguranță în afara actorului. Actorul însuși rămâne izolat — starea sa internă nu este dezvăluită.
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 — este o structură Sendable care conține doar proprietăți let de tipuri Sendable. Această abordare este best practice pentru extragerea datelor dintr-un actor. Instantaneul stării este transmis prin valoare, iar actorul nu pierde controlul asupra stării sale.
@Sendable — este un atribut pentru funcții și closure-uri care garantează că closure-ul nu capturează date non-Sendable în mod mutabil. Când un closure este transmis în Task sau unei metode actor, acesta trebuie să fie Sendable.
Compilatorul verifică dacă closure-ul @Sendable nu capturează referințe mutabile la clase. Capturarea proprietăților let ale tipurilor Sendable este permisă. Capturarea unei variabile var de tip referință va cauza o eroare, deoarece closure-ul poate fi executat simultan cu mutația.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — capturarea var mută contorul, eroare de compilare
performAsync { // ❌ Mutația var-ului capturat
print(constant)
}
Regula este simplă: un closure @Sendable poate captura doar acele date care sunt ele însele Sendable și nu vor fi mutate din exterior. Pentru clase, este permisă capturarea referinței slabe weak self dacă clasa nu este marcată ca Sendable. Aceasta previne ciclurile de retain și condițiile de cursă clasice.
Întrebări frecvente
Sendable — este un marker care spune compilatorului: „acest tip poate fi transmis în siguranță între fire”. Structurile și constantele sunt de obicei Sendable implicit, clasele — nu, cu excepția cazului în care este specificat explicit.
Tipurile valoare (struct, enum) cu proprietăți Sendable, clasele finale cu stare imutabilă, precum și toate tipurile de bază Swift: Int, String, Double, Bool, Array, Dictionary, Optional — sunt automat conforme cu Sendable.
@unchecked Sendable — este o modalitate de a declara o clasă ca Sendable fără verificarea statică a compilatorului. Dezvoltatorul garantează siguranța independent, adesea prin blocaje sau operații atomice.
Actorul își izolează starea, dar la returnarea datelor către codul extern, aceste date traversează granița de izolare. Sendable garantează că destinatarul nu se va confrunta cu condiții de cursă la utilizarea acestor date în afara actorului.
Adăugați protocolul Sendable la o clasă finală ale cărei proprietăți sunt toate constante de tipuri Sendable. Dacă clasa utilizează blocaje, se poate aplica @unchecked Sendable, dar aceasta necesită prudență.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și