← Назад к вопросам

Как определить, в каких случаях необходимо придерживаться протокола Sendable?

1.8 Middle🔥 142 комментариев
#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда необходимо придерживаться протокола Sendable?

Протокол Sendable — это ключевой механизм статической проверки безопасности данных в конкурентном коде Swift (начиная с Swift 5.5 и акторах). Он указывает компилятору, что тип безопасен для использования в разных конкурентных контекстах (например, между акторами, задачами Task). Основные случаи, когда необходимо придерживаться Sendable:

1. Передача данных между изолированными конкурентными доменами

  • При передаче значений через Task, особенно между разными акторами или изолированными функциями.
  • При использовании @Sendable-замыканий, которые захватывают внешние значения.
// Пример: передача Sendable типа в Task
struct UserData: Sendable {
    let id: String
    let score: Int
}

Task {
    let data = UserData(id: "user123", score: 100)
    await process(data: data) // Без Sendable будет предупреждение компилятора
}

2. Использование в акторах (Actor)

  • Когда актор публикует свойства или возвращает значения, которые могут использоваться вне актора.
  • При реализации @MainActor для передачи данных в UI.
actor DataManager {
    private var cache: [String: SendableData] = [:]
    
    func getData(for key: String) -> SendableData? {
        return cache[key]
    }
}

// SendableData должен быть Sendable
struct SendableData: Sendable {
    let content: String
}

3. Работа с глобальными переменными или статическими свойствами

  • Доступ к неизменяемым (let) глобальным переменным из разных задач требует Sendable.
// Глобальная конфигурация должна быть Sendable
let appConfig: AppConfiguration? = nil // AppConfiguration должен быть Sendable

struct AppConfiguration: Sendable {
    let apiEndpoint: String
}

4. Хранение в коллекциях, которые передаются между задачами

  • Например, массивы, словари или наборы, которые передаются через Task или акторы.

5. Типы, используемые в @Sendable-замыканиях

  • Все значения, захватываемые @Sendable-замыканием, должны быть Sendable.
func fetchData(completion: @Sendable (Result<Data, Error>) -> Void) {
    Task {
        let data = try await downloadData()
        completion(.success(data)) // Data и Error должны быть Sendable
    }
}

Когда Sendable НЕ требуется?

  • Локальное использование: если тип используется только в одном изолированном контексте (например, внутри одного актора).
  • Неизменяемые (immutable) типы с Sendable-полями: компилятор часто автоматически выводит Sendable для структур, содержащих только Sendable-свойства.
  • Классы с акторами или изоляцией: если класс изолирован к актору (@MainActor class), передача экземпляров внутри этого актора не требует Sendable.

Критические аспекты реализации

  1. Структуры (value types): обычно автоматически Sendable, если все их свойства Sendable.
  2. Классы (reference types): требуют ручного соответствия Sendable только если они неизменяемые (immutable) и наследники NSObject или с внутренней синхронизацией.
  3. Запрещены захваты изменяемых ссылок: Sendable-замыкания не могут захватывать изменяемые (var) не-Sendable объекты.
// Опасный пример - НЕ Sendable
class NonSendableClass {
    var data: String = ""
}

Task { [nonSendable = NonSendableClass()] in // Предупреждение компилятора!
    print(nonSendable.data)
}

Практическое правило

Применяйте Sendable явно, когда компилятор выдаёт предупреждения о безопасности данных в конкурентном коде, или при проектировании типов, которые будут использоваться в многозадачной среде. Это особенно важно в современных приложениях с активным использованием async/await и акторов, где Sendable предотвращает гонки данных и неопределённое поведение.