Sendable — що це таке, протокол потокобезпеки

Автор: IT Sectr Опубліковано: 2026-06-18 Час читання: 10 хв

Sendable — це протокол Swift, який маркує типи, безпечні для передачі між потоками. Під час роботи з конкурентністю (actor, async/await, Task) компілятор Swift вимагає, щоб усі дані, що передаються між ізольованими контекстами, відповідали Sendable. Це виключає випадкову передачу небезпечних типів, які можуть призвести до станів гонки. Протокол виступає контрактом: тип, що відповідає Sendable, гарантує відсутність внутрішнього несинхронізованого стану. За даними WWDC 2021, протокол Sendable є обов'язковим елементом при проєктуванні безпечної багатопоточної архітектури.

Головне

  • Sendable — протокол, що гарантує відсутність станів гонки при передачі між потоками
  • Value-типи (struct, enum) автоматично відповідають Sendable, якщо всі їхні властивості Sendable
  • @unchecked Sendable — обхідний механізм для класів, чию безпеку розробник бере на себе
  • Sendable замикання позначаються атрибутом @Sendable, що перевіряє захоплення змінних
  • Компілятор видає помилку, якщо не-Sendable тип передається між ізольованими контекстами

Що таке протокол Sendable у Swift?

Sendable — це протокол зі стандартної бібліотеки Swift (SE-0302), який маркує типи, безпечні для передачі між доменами ізоляції. Під доменом ізоляції розуміється actor, задача Task або @MainActor-контекст. Sendable не містить вимог — він виступає маркером безпеки.

Навіщо потрібен Sendable

До Swift 5.5 розробники могли передати будь-який об'єкт між потоками через DispatchQueue, і компілятор не перевіряв, чи це безпечно. Sendable закриває цю прогалину: тепер компілятор сам відстежує міжпотокові передачі та блокує небезпечні. Це робить конкурентний код надійнішим ще до його запуску.

swift
struct UserProfile: Sendable {
    let name: String
    let age: Int
}

class NonSendableClass {
    var counter: Int = 0
}

У прикладі UserProfile може бути безпечно переданий між потоками, оскільки це структура з константними властивостями Sendable-типів. NonSendableClass викличе помилку компіляції при спробі передачі через await або в Task.

Які типи відповідають Sendable за замовчуванням

Sendable-типи у Swift поділяються на три категорії: value-типи з Sendable-властивостями, final-класи з незмінним станом і функції/замикання, позначені @Sendable. Компілятор автоматично виводить відповідність Sendable для value-типів.

Value-типи та автоматична відповідність

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. Це робить композицію типів безпечною за замовчуванням. Розробнику достатньо стежити за кастомними класами.

Sendable і класи: @unchecked Sendable

@unchecked Sendable — це механізм, що дозволяє класу явно оголосити себе Sendable, обходячи перевірки компілятора. Розробник бере на себе відповідальність за потокобезпеку такого класу. Це корисно для Objective-C-мостів та оптимізованих структур.

Коли використовувати @unchecked Sendable

@unchecked Sendable застосовується, коли клас внутрішньо гарантує безпеку через блокування або атомарні операції, але компілятор не може це перевірити статично. Наприклад, клас із os_unfair_lock або pthread_mutex_t — його потокобезпека забезпечується кодом, але Swift не бачить цього.

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, варто переконатися, що тип дійсно не може бути переписаний як value-тип.

Sendable у поєднанні з Actor

Actor і Sendable — дві сторони однієї медалі. Actor ізолює свій стан, але для обміну даними із зовнішнім світом він повинен повертати Sendable-типи. Якщо метод actor повертає не-Sendable тип, компілятор видає попередження або помилку.

Передача даних через Sendable

Коли actor надсилає дані зовнішньому коду, ці дані перетинають межу ізоляції. Sendable гарантує, що отримувач може безпечно використовувати їх поза actor. Сам actor залишається ізольованим — його внутрішній стан не розкривається.

swift
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 — це атрибут для функцій і замикань, який гарантує, що замикання не захоплює не-Sendable дані у змінному вигляді. Коли замикання передається в Task або метод actor, воно має бути Sendable.

Перевірка захоплення в @Sendable замиканнях

Компілятор перевіряє, що @Sendable замикання не захоплює змінні посилання на класи. Захоплення let-властивостей Sendable-типів дозволено. Захоплення var-змінної посилального типу призведе до помилки, оскільки замикання може виконуватися одночасно з мутацією.

swift
func performAsync(operation: @Sendable () -> Void) {
    Task {
        await operation()
    }
}

let constant = "Safe"
var counter = 0
// counter — захоплення var змінює counter, помилка компіляції
performAsync { // ❌ Мутація захопленої змінної
    print(constant)
}

Правило просте: @Sendable замикання може захоплювати тільки ті дані, які самі є Sendable і не будуть мутуватися ззовні. Для класів допустиме захоплення слабкого посилання weak self, якщо клас не позначений як Sendable. Це запобігає класичним retain cycle та станам гонки.

Поширені запитання

Що таке Sendable у Swift простими словами?

Sendable — це маркер, який каже компілятору: «цей тип можна безпечно передавати між потоками». Структури та константи зазвичай Sendable за замовчуванням, класи — ні, якщо це не зазначено явно.

Які типи автоматично відповідають Sendable?

Value-типи (struct, enum) з Sendable-властивостями, final-класи з незмінним станом, а також усі базові типи Swift: Int, String, Double, Bool, Array, Dictionary, Optional — відповідають Sendable автоматично.

Що таке @unchecked Sendable?

@unchecked Sendable — це спосіб оголосити клас Sendable без статичної перевірки компілятора. Розробник гарантує безпеку самостійно, часто через блокування або атомарні операції.

Навіщо Sendable потрібен для Actor?

Actor ізолює свій стан, але при поверненні даних зовнішньому коду ці дані перетинають межу ізоляції. Sendable гарантує, що отримувач не зіткнеться зі станами гонки при використанні цих даних поза actor.

Як зробити клас Sendable у Swift?

Додайте протокол Sendable до final-класу, всі властивості якого — константи Sendable-типів. Якщо клас використовує блокування, можна застосувати @unchecked Sendable, але це потребує обережності.

Підсумки

  • Sendable — протокол-маркер для безпечної міжпотокової передачі даних
  • Value-типи автоматично відповідають Sendable при виконанні умов
  • Класи потребують явного зазначення Sendable або @unchecked Sendable
  • Actor повертає дані зовнішньому коду тільки через Sendable-типи
  • @Sendable атрибут замикань перевіряє коректність захоплення змінних
  • Компілятор статично перевіряє відповідність Sendable, запобігаючи станам гонки на етапі збірки

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також