Какую гарантию безопасности данных теряет свойство actor после объявления nonisolated(unsafe)?
Свойство с nonisolated(unsafe) больше не защищается изоляцией actor на уровне компилятора. К нему можно обращаться без перехода в actor-контекст, но это не добавляет синхронизацию: одновременное чтение и изменение могут привести к гонке данных.
Такая пометка отключает проверку безопасности, а не делает доступ безопасным. Ответственность за взаимное исключение, атомарность и срок жизни объекта полностью переходит к разработчику.
Модель конкурентности Swift стремится проверять изоляцию данных статически: состояние actor доступно только через его изолированный контекст, а передаваемые между конкурентными контекстами значения должны соответствовать требованиям Sendable.
Однако компилятор не всегда способен доказать безопасность существующего кода. Это встречается при миграции старых библиотек, взаимодействии с C- и Objective-C-кодом, использовании внешней блокировки или структуры, чья потокобезопасность обеспечивается не типовой системой Swift.
nonisolated(unsafe) предназначен для явного отказа от такой проверки в узком месте. Это escape hatch для совместимости и постепенной миграции, а не рекомендуемый способ проектирования общего состояния.
Обычное изменяемое свойство actor изолировано: внешний код не может читать или менять его напрямую без обращения к actor. После объявления nonisolated(unsafe) это ограничение для данного свойства снимается.
Например, один контекст может обновлять значение через actor, пока другой читает его напрямую. Если между этими операциями нет отдельной синхронизации, возникает гонка данных. Возможны несогласованные значения, нарушение инвариантов и неопределённое поведение на уровне модели памяти.
Особенно опасно хранить таким образом ссылочный объект. Даже если ссылка сама читается без проблем, её изменяемое внутреннее состояние не становится защищённым actor.
nonisolated(unsafe) изменяет проверку доступа к конкретному объявлению: компилятор перестаёт требовать actor-изоляцию там, где обычно потребовался бы изолированный вызов. При этом свойство не начинает выполняться на отдельном потоке, не получает автоматическую блокировку и не становится атомарным.
Actor защищает только то состояние, которое остаётся его изолированным состоянием. Если доступ к свойству разрешён из разных конкурентных контекстов, безопасность нужно обеспечить другим механизмом:
Пометка также не превращает значение в Sendable. Она лишь подавляет часть диагностик изоляции; требования к внутренней потокобезопасности значения и его ссылочных компонентов никуда не исчезают.
Использовать escape hatch допустимо, когда есть явно доказанный внешний инвариант: например, каждое чтение и изменение проходит через одну и ту же блокировку. Такой инвариант следует локализовать и документировать, иначе последующая модификация кода легко разрушит предполагаемую безопасность.
В приложении actor хранит последнее состояние кеша, а синхронный legacy-компонент должен быстро получить его без async-вызова. Команда рассматривает три варианта.
Первый вариант — пометить свойство nonisolated(unsafe). Он прост и не требует изменения API, но безопасен только при доказанной внешней синхронизации. Если legacy-компонент читает значение произвольно, вариант отклоняется: actor больше не защищает это состояние.
Второй вариант — оставить свойство изолированным и добавить асинхронный метод чтения. Это сохраняет модель безопасности, но меняет вызывающий код и может быть неудобно для строго синхронного legacy-интерфейса.
Третий вариант — хранить отдельный неизменяемый снимок, публикация которого выполняется через контролируемый механизм синхронизации. Это добавляет небольшую сложность и допускает устаревшее значение, зато не открывает внутреннее изменяемое состояние actor.
Предпочтительно выбрать второй вариант, а при обязательном синхронном API — третий. nonisolated(unsafe) оставляют только при наличии реального внешнего протокола доступа, который можно проверить и поддерживать независимо от actor.
Нет. Атомарность не следует ни из actor, ни из самой пометки. Для некоторых простых типов отдельная операция чтения или записи может выглядеть неделимой на конкретной платформе, но это не является общей гарантией безопасной конкурентности и не защищает составные операции вроде «прочитать, проверить, изменить».
Нет. Прямое чтение всё равно конкурирует с записью, если они могут выполняться одновременно. Кроме того, читатель может увидеть состояние в момент, когда связанные поля уже обновлены не полностью; для согласованного снимка нужно синхронизировать публикацию всего состояния, а не только отдельную запись.
Нет. Безопасность ссылки и безопасность объекта — разные свойства. Несколько задач могут получить один и тот же экземпляр и одновременно менять его внутренние поля; actor, которому принадлежала ссылка, не защищает дальнейшие вызовы методов этого объекта после передачи наружу.