Допустим, синхронный инициализатор actor напрямую изменяет его изолированное хранимое состояние. Почему такой доступ допустим без await?
Такой доступ допустим, потому что во время инициализации экземпляр actor ещё не опубликован и не может быть доступен другим конкурентным задачам. Пока объект не завершил инициализацию, его состояние не нуждается в межзадачной сериализации; после публикации действуют обычные правила изоляции actor.
Actors были введены в модель конкурентности Swift, чтобы защищать изменяемое состояние без ручного управления блокировками. Основная проблема возникает после публикации объекта: несколько задач могут одновременно попытаться обратиться к одному состоянию.
Инициализация является особой фазой жизненного цикла объекта. Swift учитывает, что до завершения этой фазы ссылка на actor не должна использоваться конкурентно, поэтому требование await для первичной настройки состояния не имело бы практического смысла.
Если бы каждое присваивание stored property в инициализаторе требовало межакторного вызова, создание actor стало бы излишне сложным и не отражало бы реальный риск гонки. В этот момент ещё нет полностью созданного экземпляра, который могли бы одновременно наблюдать разные задачи.
Риск появляется, если попытаться передать self наружу до завершения инициализации. Тогда можно получить обращение к частично созданному объекту, нарушить инварианты и запустить конкурентную работу до того, как все свойства готовы.
В инициализаторе actor Swift разрешает напрямую инициализировать и изменять его stored properties. Это часть специального правила инициализации, а не отмена actor-изоляции.
После завершения init обращение к count из внешней задачи уже является изолированным доступом и требует взаимодействия с actor, обычно через await. Сам вызов синхронного конструктора actor не превращается в выполнение метода на actor-исполнителе: конструктор должен только корректно создать экземпляр.
До завершения инициализации нельзя безопасно «утечь» ссылкой на self: например, передать её в сторонний объект, сохранить в глобальном состоянии или запустить работу, которая может увидеть недостроенный actor. Запуск фоновой задачи из инициализатора также не следует использовать для установки обязательных инвариантов: такая задача выполняется позже, поэтому объект может быть опубликован в промежуточном состоянии.
Если для создания объекта нужны асинхронные данные, обычно применяют асинхронную фабрику: сначала получают и проверяют данные, затем вызывают обычный инициализатор. Это сохраняет правило, что после публикации actor уже находится в полностью валидном состоянии.
Сервис кэширования должен получить начальную конфигурацию из файла или сети. Рассматривались два варианта: создать actor сразу и загрузить обязательное состояние в отдельной Task, либо выполнить загрузку в асинхронной фабрике перед созданием actor.
Первый вариант проще визуально, но допускает обращения к кэшу до завершения загрузки и требует дополнительного состояния вроде isReady. Кроме того, ошибка или отмена фоновой задачи может оставить actor в неполностью пригодном состоянии.
Выбран второй вариант: фабрика асинхронно загружает конфигурацию, проверяет её и передаёт готовые значения в синхронный инициализатор. В результате после получения ссылки на actor его инварианты уже установлены, а последующий доступ к изменяемому состоянию надёжно сериализуется actor-изоляцией.
1. Можно ли вызвать синхронный инициализатор actor из фоновой задачи без await?
Да, сам синхронный вызов конструктора не требует await, поскольку создание экземпляра не является обращением к уже опубликованному actor-состоянию. Однако аргументы конструктора всё равно должны быть допустимы для передачи через соответствующую границу конкурентности: например, передача несамопередаваемого значения из другой изоляции может быть запрещена проверкой конкурентности.
2. Можно ли запустить Task из инициализатора actor для настройки его состояния?
Технически после завершения инициализации это может быть возможно, но такая задача выполняется асинхронно и не является частью атомарного создания объекта. Код, получивший actor сразу после конструктора, может обратиться к нему раньше задачи настройки. Поэтому обязательные свойства и инварианты следует устанавливать синхронно в init, а асинхронную подготовку выполнять до создания actor или явно отражать состояние готовности в API.
3. Означает ли особое правило инициализации, что состояние actor вообще не защищено?
Нет. Исключение действует только во время создания экземпляра, когда он ещё не доступен конкурентным задачам. После завершения init и публикации ссылки обычный доступ к изолированному изменяемому состоянию снова должен проходить через actor-изоляцию; прямой доступ из внешнего кода будет ошибкой модели конкурентности или потребует явно небезопасного обхода.