Какие ограничения накладывает @Sendable на значения, захваченные замыканием?
Замыкание типа @Sendable должно быть безопасно передаваемым между конкурентными контекстами. Поэтому захваченные значения должны поддерживать Sendable, а захват изменяемого общего состояния обычно запрещается проверкой строгой конкурентности.
@Sendable не добавляет синхронизацию автоматически: он устанавливает проверяемый компилятором контракт о безопасности передачи замыкания и его захватов.
До появления структурированной конкурентности замыкания часто передавались в фоновые задачи без явной проверки того, безопасно ли захваченное состояние для одновременного доступа. Это создавало риск гонок данных и неопределённых результатов.
Sendable и @Sendable появились как часть модели конкурентности Swift. Их задача — обнаруживать потенциально небезопасную передачу значений между задачами на этапе компиляции, а не исправлять проблему во время выполнения.
Замыкание может захватить локальную переменную, экземпляр класса, массив или другой объект. Если это замыкание выполняется в другом конкурентном контексте, исходный владелец и замыкание могут обращаться к одному состоянию одновременно.
Особенно опасны изменяемые ссылочные объекты: копирование ссылки не создаёт независимую копию объекта. Если захваченный тип не является безопасным для передачи, компилятор должен отвергнуть такой захват или выдать диагностику в режиме строгой проверки конкурентности.
Значение, захваченное @Sendable-замыканием, должно быть допустимо для передачи между конкурентными контекстами. Для структур и перечислений это обычно означает, что все их хранимые свойства также являются Sendable. Стандартные значения вроде Int, String и массивов Sendable-элементов соответствуют этому контракту.
Для ссылочных типов одного факта, что объект является классом, недостаточно. Класс должен обеспечивать безопасный доступ к своему состоянию, например быть неизменяемым и корректно объявленным как Sendable, использовать изоляцию актора или другую синхронизацию. Простое добавление безусловного соответствия Sendable может подавить проверку, но возлагает ответственность за безопасность на разработчика.
Захват через список захвата не устраняет требования. Он может зафиксировать текущее значение ссылки или значения, однако захваченный ссылочный объект всё ещё остаётся общим объектом. Для изменяемого значения безопаснее передавать независимую Sendable-копию либо обращаться к состоянию через актор.
В первом случае замыкание захватывает неизменяемое Sendable-значение. Во втором захватывается изменяемый экземпляр обычного класса; при строгой проверке конкурентности такой код обычно диагностируется, поскольку компилятор не может доказать отсутствие гонки данных.
Главный компромисс таков: строгая проверка может потребовать дополнительного копирования, изоляции через актор или изменения архитектуры, зато уменьшает число ошибок, связанных с общим изменяемым состоянием. @Sendable проверяет форму и типы захватов, но не доказывает корректность произвольной внутренней логики самого типа.
Сервис загружает данные в фоновой задаче и принимает замыкание обратного вызова. В callback захватывался объект-модель с изменяемыми свойствами. Передача такого замыкания как @Sendable привела к ошибке компиляции.
Первый вариант — объявить класс безусловно Sendable. Это быстро, но опасно: компилятор перестанет защищать код, хотя состояние всё ещё может изменяться одновременно.
Второй вариант — захватывать снимок данных в неизменяемой структуре. Он безопасен и прост, но callback не сможет изменить исходную модель напрямую.
Третий вариант — поместить изменяемое состояние в actor, а из замыкания обращаться к нему асинхронно. Этот вариант выбран, когда состояние действительно должно быть общим: актор сериализует доступ, а стоимостью становятся асинхронные вызовы и необходимость учитывать изоляцию.
let?Нет. let запрещает переназначить саму переменную, но не делает автоматически безопасным объект, на который она ссылается. Неизменяемая ссылка на изменяемый несинхронизированный класс всё ещё может привести к гонке; важны свойства типа и способ доступа к ним.
@Sendable замыкание потокобезопасным?Нет. Атрибут задаёт контракт передачи и позволяет компилятору проверять захваты. Он не блокирует доступ к памяти, не добавляет mutex и не сериализует операции. Потокобезопасность внутренней логики должна обеспечиваться неизменяемостью, актором, блокировкой или другим подходящим механизмом.
Сам по себе — нет. Список захвата фиксирует ссылку в момент создания замыкания, но не копирует объект. Если объект изменяемый и не соответствует требованиям безопасной передачи, проблема общего состояния сохраняется. Список захвата полезен для фиксации независимого значения, но для ссылочного объекта нужно отдельно доказать или обеспечить его Sendable-безопасность.