Какую гарантию даёт Sendable при передаче значения между конкурентными задачами Swift?
Sendable означает, что значение разрешено безопасно передавать через границу изоляции конкурентности: компилятор может проверить, что такая передача не создаёт неконтролируемого совместного доступа к изменяемому состоянию. Это не делает операции атомарными, не добавляет блокировки и не гарантирует потокобезопасность произвольного ссылочного объекта.
До появления встроенной модели конкурентности Swift разработчики часто передавали данные между очередями GCD или потоками вручную. Компилятор обычно не мог проверить, что объект не изменяется одновременно из нескольких мест, поэтому ошибки проявлялись только во время выполнения.
Sendable стал частью статической модели безопасности Swift Concurrency. Он переносит часть проверки с тестирования и соглашений команды на этап компиляции, особенно при переходе между задачами, акторами и другими изолированными контекстами.
Представим, что задача получает ссылку на изменяемый объект, а другая задача одновременно меняет тот же объект. Даже если передача ссылки синтаксически допустима, чтение и запись могут пересекаться, что приводит к гонке данных и неопределённому результату.
Наличие Sendable не решает эту проблему автоматически. Оно лишь задаёт контракт для передачи: тип должен быть безопасен для использования в другом конкурентном контексте или должен предоставлять доступ к своему состоянию через механизм изоляции.
При переходе значения через границу изоляции компилятор проверяет его соответствие Sendable. Для структур и перечислений безопасность обычно выводится из того, что все их хранимые элементы сами являются Sendable; копирование такого значения не создаёт общего изменяемого состояния.
Ссылочные типы требуют особого внимания. Неизменяемый класс с корректно изолированным состоянием может соответствовать требованиям, но произвольный изменяемый класс нельзя считать безопасным только потому, что его экземпляр передали в другую задачу. Актор решает другой аспект: его ссылка может передаваться, а доступ к изолированному состоянию выполняется через actor isolation.
Sendable не означает атомарность. Даже у безопасно передаваемого значения последовательность из нескольких операций не становится неделимой. Также этот протокол не синхронизирует внутреннее состояние и не превращает небезопасный класс в безопасный.
Аннотация @unchecked Sendable отключает часть проверок компилятора и переносит ответственность на разработчика. Её применение оправдано только при наличии собственной проверенной синхронизации, например блокировок или неизменяемого внутреннего состояния; это не исправление ошибки само по себе.
Замыкания, передаваемые в конкурентные контексты, могут иметь требование @Sendable. Тогда компилятор дополнительно проверяет захваченные значения. Такой контроль предотвращает захват несоответствующих типов, но не доказывает корректность любой сложной логики синхронизации.
В примере значение Snapshot содержит только безопасно передаваемое состояние. Counter является изменяемым ссылочным объектом, поэтому его захват в Task.detached не должен считаться безопасным без дополнительной изоляции.
Сервис загрузки изображений передавал результат декодирования из фоновой задачи в задачу обновления интерфейса. Результатом был изменяемый ссылочный объект, который одновременно использовался кэшем. Попытка пометить класс как @unchecked Sendable убрала диагностику, но оставила риск гонки при изменении метаданных.
Рассматривались три варианта. Общая блокировка вокруг объекта могла сохранить минимальные изменения, но усложняла владение блокировкой и увеличивала риск взаимных блокировок. Перенос объекта внутрь актора обеспечивал последовательный доступ, но требовал асинхронного обращения к каждому изменяемому полю. Создание неизменяемого снимка данных перед передачей давало копирование, зато устраняло общее изменяемое состояние.
Был выбран снимок как struct, соответствующий Sendable, а кэш оставили под контролем отдельного актора. В результате граница передачи стала проверяемой компилятором, интерфейс получал независимые данные, а операции с кэшем сохраняли последовательную изоляцию.
Нет. Sendable описывает допустимость передачи значения между конкурентными доменами, но не превращает методы объекта в атомарные и не добавляет синхронизацию. Если тип имеет изменяемое состояние, его безопасность должна обеспечиваться неизменяемостью, actor isolation или явной синхронизацией.
Актор сам является изолированным владельцем состояния. Передаётся ссылка на него, но прямой доступ к изолированным данным за пределами актора запрещён; взаимодействие проходит через его изолированные методы и свойства. Поэтому безопасность обеспечивается не копированием состояния, а сериализацией доступа внутри актора.
Компилятор принимает объявленное соответствие без обычного доказательства безопасности типа. Это может быть корректно для класса, внутри которого все обращения защищены одной и той же блокировкой, но компилятор не проверит, что разработчик действительно использует её во всех путях доступа. Ошибка в таком контракте возвращает проблему в область гонок данных и делает её особенно трудной для диагностики.