Старый Swift модуль импортирован с @preconcurrency: что изменится при проверке передачи его типов между кон...

Старый Swift-модуль импортирован с @preconcurrency: что изменится при проверке передачи его типов между конкурентными задачами?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

@preconcurrency ослабляет статическую проверку конкурентности для деклараций импортированного модуля, который ещё не адаптирован к современным требованиям Swift Concurrency. В частности, компилятор может не сообщить об отсутствии Sendable или о проблемном пересечении границ изоляции.

Это не делает типы безопасными, не добавляет им соответствие Sendable и не вводит синхронизацию во время выполнения. Атрибут является временным механизмом совместимости при миграции.

Исторический контекст

Swift Concurrency появилась после большого количества существующего кода и библиотек, созданных до появления async/await, акторов и Sendable. Немедленное включение строгой проверки для всех таких модулей сделало бы миграцию крупных проектов практически непосильной.

@preconcurrency позволяет подключить старый модуль и временно уменьшить количество диагностик, пока его публичные типы и методы не будут проверены и размечены для конкурентного использования.

Постановка проблемы

Предположим, старый модуль возвращает ссылочный объект с изменяемым состоянием. Такой объект передают из одной задачи в другую через границу изоляции, но модуль импортирован с @preconcurrency.

Компилятор может пропустить этот код, хотя две задачи потенциально обращаются к одному объекту одновременно. Если объект не защищён актором, очередью или другой синхронизацией, сохраняется риск гонки данных, повреждения состояния и недетерминированного поведения.

Главная опасность — принять отсутствие ошибки компиляции за доказательство безопасности. @preconcurrency скрывает часть проблем миграции, но не устраняет саму проблему.

Подробное решение

При обычной строгой проверке Swift анализирует пересечение границ конкурентности: например, передачу значения в другую задачу или вызов метода с другого актора. Для этого используются сведения о Sendable, изоляции акторов и глобальных акторов.

При @preconcurrency import компилятор рассматривает объявления этого модуля как созданные до внедрения проверок конкурентности. Поэтому диагностика, связанная с такими объявлениями, может быть ослаблена или подавлена. Область действия относится к импортированным декларациям, а не ко всему коду приложения.

Атрибут не изменяет:

  • фактическую ссылочную семантику объектов;
  • наличие или отсутствие блокировок и акторов;
  • поведение задач и планировщика;
  • соответствие типа протоколу Sendable на уровне исполнения.

Практически @preconcurrency следует применять как временную границу миграции. Нужно зафиксировать импортируемые типы, которые пересекают конкурентные границы, проверить их потокобезопасность, заменить небезопасные объекты на Sendable-значения или изолировать состояние актором, а затем убрать атрибут и вернуть строгую диагностику.

Ситуация из практики

Приложение использует старую сетевую библиотеку, чьи модели являются изменяемыми классами. После перехода проекта на строгую проверку конкурентности сборка начинает выдавать множество диагностик, поэтому команду временно добавляет @preconcurrency к импорту.

Вариант оставить атрибут навсегда прост, но он скрывает новые ошибки на границе библиотеки. Вариант немедленно переписать всю библиотеку безопаснее, однако может быть слишком дорогим и остановить миграцию.

Разумное решение — временно сохранить атрибут, но разрешить передачу объектов библиотеки только через один изолированный адаптер. Адаптер сериализует доступ или преобразует результаты в неизменяемые Sendable-значения. После обновления или обёртывания библиотеки @preconcurrency удаляют и проверяют, что компилятор снова видит все потенциально небезопасные границы.

Что кандидаты часто упускают

  1. Создаёт ли @preconcurrency соответствие Sendable для импортированного типа?

Нет. Он меняет правила диагностики при использовании декларации, но не добавляет протоколу фактическое соответствие и не доказывает безопасность типа. Изменяемый класс остаётся изменяемым классом, поэтому его конкурентное использование всё ещё требует изоляции или собственной синхронизации.

  1. Меняет ли @preconcurrency поведение программы во время выполнения?

Нет. Это указание компилятору, а не runtime-механизм. Атрибут не ставит блокировки, не переключает выполнение на актор и не сериализует вызовы; он лишь позволяет пройти проверку кода, который иначе мог бы получить диагностику.

  1. Когда допустимо удалить @preconcurrency?

После проверки обновлённого модуля и всех его типов, пересекающих границы конкурентности. Если библиотека получила корректные Sendable-соответствия и annotations изоляции, атрибут обычно больше не нужен. Его удаление важно как контрольный этап: вновь появившиеся ошибки показывают места, где приложение всё ещё опиралось на скрытую компилятором проблему.