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Да (без явного указания)Все associated values 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 — capturing var mutates counter, compile error
performAsync { // ❌ Mutation of captured var
    print(constant)
}

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

Часто задаваемые вопросы

Что такое 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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