Sendable — е Swift протокол, който маркира типове, безопасни за пренасяне между нишки. При работа с конкурентност (actor, async/await, Task) Swift компилаторът изисква всички данни, пренасяни между изолирани контекстове, да съвпадат с Sendable. Това изключва случайното пренасяне на небезопасни типове, които могат да доведат до гонки на данни. Протоколът действа като договор: тип, които отговаря на Sendable, гарантира липсата на вътрешно несинхронизирано състояние. Според WWDC 2021, протоколът Sendable е задължителен елемент при проектирането на безопасна многонишкова архитектура.
Основни
Sendable — е протокол от стандартната библиотека на Swift (SE-0302), който маркира типове, безопасни за пренасяне между домейни на изолация. Под домейн на изолация се разбира actor, Task задача или @MainActor контекст. Sendable не съдържа изисквания — действа като маркер за безопасност.
Преди Swift 5.5 разработчиците могаха да пренасят всеки обект между нишки чрез DispatchQueue, и компилаторът не проверяваше дали е безопасно. Sendable затваря този пробив: сега компилаторът сам проследя междунишковите пренасяния и блокира небезопасните. Това прави конкурентния код по-надежден още преди изпълняването му.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
В примера UserProfile може да бъде безопасно пренасен между нишки, тъй като е структура с константни свойства на Sendable типове. NonSendableClass ще причини грешка при компилация при опит за пренасяне чрез await или в Task.
Sendable типове в Swift се разделят на три категории: стойностни типове с Sendable свойства, финални класове с непроменливо състояние и функции/затваряния, маркирани с @Sendable. Компилаторът автоматично извежда съответствието с Sendable за стойностните типове.
Struct, enum и tuple автоматично стават Sendable, ако всички тяхни свойства и асоциирани стойности също са Sendable. Това е консервативен подход: ако дори едно свойство не отговаря на Sendable, целият тип няма да бъде считан за безопасен.
| Тип | Sendable | Условие |
|---|---|---|
| Struct | Да (без изрично посочване) | Всички свойства Sendable |
| Enum | Да (без изрично посочване) | Всички асоциирани стойности Sendable |
| Final class | Да (с изрично посочване) | Всички let свойства Sendable, без var |
| Non-final class | Не | Не може да бъде Sendable поради наследяване |
Всички вградени Swift типове — Int, String, Double, Bool, Optional, Array, Dictionary, Set — отговарят на Sendable. Това прави композицията на типове по подразбиране безопасна. Разработчикът трябва да наблюдава само за потребителски класове.
@unchecked Sendable — е механизъм, който позволява на един клас явно да се обяви като Sendable, заобиколявайки проверките на компилатора. Разработчикът поема отговорността за нишковата безопасност на такъв клас. Полезно е за Objective-C мостове и оптимизирани структури.
@unchecked Sendable се прилага, когато един клас вътрешно гарантира безопасност чрез заключването на блокиовки или атомни операции, но компилаторът не може да ги провери статично. Например, клас с os_unfair_lock или pthread_mutex_t — нишковата му безопасност се осигурява от кода, но Swift не вижда това.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
Използвайте @unchecked Sendable внимателно. Това е ясен сигнал за други разработчици: „Проверих го, безопасно е”. Грешка в имплементацията може да доведе до трудно за откриване гонки. Преди да приложите @unchecked, убедете се, че типът наистина не може да бъде преписан като стойностен тип.
Actor и Sendable са две страни на една и съща монета. Actor изолира своето състояние, но за обмяна на данни с външния свят трябва да върща Sendable типове. Ако метод на actor върща не-Sendable тип, компилаторът дава предупреждение или грешка.
Когато actor изпраща данни на външен код, тези данни пресичат границата на изолация. Sendable гарантира, че получателят може да ги използва безопасно ивн извн actor-а. Самият actor остава изолиран — вътрешното му състояние не се разкрива.
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 — е Sendable структура, която съдържа само let свойства на Sendable типове. Този подход е best practice за извличане на данни от actor. Снимката на състоянието се пренася по стойност, и actor-ът не губи контрол над своето състояние.
@Sendable — е атрибут за функции и затваряния, който гарантира, че затварянето не улавя не-Sendable данни по променлив начин. Когато затварянето се пренася на Task или метод на actor, то трябва да бъде Sendable.
Компилаторът проверява, че @Sendable затварянето не улавя променливи референции към класове. Улавянето на let свойства на Sendable типове е разрешено. Улавянето на var променлива на референтен тип ще причини грешка, тъй като затварянето може да се изпълни едновременно с мутацията.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — улавянето на var мутира брояча, грешка при компилация
performAsync { // ❌ Мутация на уловен var
print(constant)
}
Правилото е просто: @Sendable затварянето може да улавя само онези данни, които сами са Sendable и няма да бъдат мутирани отвън. За класовете е разрешено улавянето на слабата референция weak self, ако класът не е маркиран като Sendable. Това предотвратява класическите retain cycle и race condition.
Често задавани въпроси
Sendable — е маркер, който казва на компилатора: „Този тип може безопасно да се пренася между нишки”. Структурите и константите обикновено са Sendable по подразбиране, класовете — не, освен ако е изрично посочено.
Стойностните типове (struct, enum) с Sendable свойства, финалните класове с непроменливо състояние, а също всички базови Swift типове: Int, String, Double, Bool, Array, Dictionary, Optional — автоматично отговарят на Sendable.
@unchecked Sendable — е начин да се обяви един клас като Sendable без статична проверка от компилатора. Разработчикът самостоятелно гарантира безопасността, често чрез заключването на блокиовки или атомни операции.
Actor-ът изолира своето състояние, но при върщането на данни на външен код тези данни пресичат границата на изолация. Sendable гарантира, че получателят няма да се срещне с гонки при използването на тези данни ивн извн actor-а.
Добавете протокол Sendable към финален клас, чиито всички свойства са константи на Sendable типове. Ако класът използва заключването на блокиовки, може да се приложи @unchecked Sendable, но това изисква внимание.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също