Насколько actor защищает изменяемый ссылочный объект после его возврата вызывающему коду?
Actor защищает только доступ к ссылке через изолированные методы самого actor. Если actor возвращает наружу изменяемый объект-ссылку, вызывающий код получает алиас и может изменить объект в обход actor. Поэтому изоляция actor не распространяется автоматически на внутреннее состояние всего графа объектов.
Actors появились в Swift Concurrency как модель изоляции изменяемого состояния. Вместо ручной синхронизации каждый actor предоставляет последовательный доступ к своим изолированным свойствам и методам.
Эта модель решает проблему гонок при прямом доступе к состоянию actor, но не может автоматически контролировать все ссылки, которые были выданы наружу. Если один и тот же изменяемый объект доступен из нескольких контекстов, его собственная синхронизация всё ещё необходима.
Рассмотрим actor, который хранит объект-контейнер и возвращает его через асинхронный метод. После получения ссылки вызывающий код может изменить контейнер напрямую, пока actor продолжает использовать тот же объект.
В результате часть операций проходит через изоляцию actor, а часть — мимо неё. Это создаёт гонки данных, несогласованные снимки состояния и ошибки, которые не устраняются тем фактом, что сам actor обрабатывает свои методы последовательно.
Actor изолирует доступ к своим stored properties и actor-isolated методам. Однако при возврате ссылочного типа наружу копируется не объект, а только ссылка на него. Actor и вызывающий код начинают совместно владеть одной изменяемой сущностью.
@unchecked Sendable здесь намеренно показывает опасный случай: разработчик вручную утверждает, что тип безопасен для передачи между конкурентными контекстами, но компилятор уже не проверяет это утверждение. Такая аннотация не добавляет блокировок и не превращает обычный класс в потокобезопасный объект.
Без @unchecked Sendable строгая проверка конкурентности обычно не позволит безопасно передать подобный изменяемый класс через границу конкурентного доступа. Это полезная диагностика, но даже успешная компиляция не означает, что объект защищён: безопасность зависит от реализации типа.
Надёжные варианты обычно такие:
У value types есть важная оговорка: копирование значения не всегда означает глубокое копирование всего содержимого. Структура может содержать ссылки на изменяемые классы, поэтому проверять нужно весь передаваемый граф объектов, а не только внешний тип.
Компромисс заключается в выборе между безопасностью и стоимостью копирования. Снимок проще анализировать и безопаснее передавать, но он может быть дорогим для больших данных; общий потокобезопасный объект экономит копирование, но усложняет протокол синхронизации и ответственность за его корректность.
В приложении actor хранил кэш изображений и возвращал вызывающему коду объект, содержащий изменяемые метаданные изображения. Экран получал этот объект и обновлял его напрямую, пока actor одновременно очищал кэш; периодически появлялись противоречивые метаданные.
Рассматривались три варианта. Возврат внутреннего объекта был самым быстрым, но оставлял алиасы и гонки. Добавление @unchecked Sendable убирало часть предупреждений компилятора, однако лишь скрывало проблему. Полная блокировка всего кэша могла сохранить общую ссылку, но увеличивала сложность и время удержания блокировки.
Выбрали возврат неизменяемой структуры-снимка с необходимыми полями, а изменения стали выполняться командами actor. В результате граница владения стала явной: экран работал со снимком, а actor оставался единственным владельцем изменяемого состояния.
Дополнительный вопрос: достаточно ли сделать свойство actor let, чтобы возвращаемый объект был безопасен?
Нет. let запрещает заменить саму ссылку внутри actor, но не запрещает изменить объект, на который она указывает. Если свойство имеет ссылочный тип с изменяемыми полями, объект всё равно может быть изменён через любой полученный алиас.
Дополнительный вопрос: делает ли Sendable изменяемый класс потокобезопасным?
Нет. Обычный изменяемый класс не должен автоматически считаться безопасным для конкурентной передачи. Если разработчик использует @unchecked Sendable, он принимает на себя обязанность доказать безопасность: например, обеспечить синхронизацию каждого доступа или сделать состояние неизменяемым. Сам протокол Sendable не добавляет механизмов взаимного исключения.
Дополнительный вопрос: безопасно ли возвращать из actor структуру, содержащую ссылочные поля?
Не обязательно. Внешняя структура может копироваться по значению, но её ссылочные поля будут указывать на те же объекты. Такой результат безопасен только тогда, когда вложенные ссылки неизменяемы или самостоятельно синхронизированы; иначе алиасинг просто скрыт одним уровнем вложенности.