В приложении состояние экрана помечено @MainActor, но к нему обращаются из фоновой async-задачи: какую гарантию даёт такая изоляция?
@MainActor гарантирует, что изолированное им изменяемое состояние доступно только через главный глобальный актор. Конкурирующие обращения к этому состоянию сериализуются его исполнителем, поэтому две задачи не выполняют изолированные операции над ним одновременно.
Это не означает, что любой код, вызванный из метода @MainActor, автоматически безопасен: передача ссылочных объектов наружу, незащищённое состояние внутри них и обход изоляции по-прежнему могут создать гонки.
До появления Swift Concurrency доступ к состоянию интерфейса обычно вручную переключали на главный поток через GCD или аналогичные механизмы. Компилятор при этом не проверял, что все обращения к данным действительно выполняются в нужном контексте.
Глобальные акторы, включая MainActor, перенесли это правило на уровень модели изоляции Swift. Цель подхода — описать владельца состояния в типах и обнаруживать некорректный доступ при компиляции, а не полагаться только на дисциплину разработчика.
Пусть фоновая задача загружает данные, а экран одновременно изменяется из-за пользовательского действия. Если оба пути напрямую меняют одно состояние, порядок операций становится зависимым от планирования задач. Это может привести к потере обновления, несогласованным значениям и трудно воспроизводимым ошибкам интерфейса.
Помещение состояния под MainActor делает его изолированным: фоновая задача не может произвольно прочитать или изменить его без перехода в контекст главного актора. Ошибочное обращение обычно фиксируется компилятором; добавление await выражает потенциальную приостановку и передачу управления.
Атрибут @MainActor можно применять к свойству, методу или целому типу. Для типа это означает, что его изолированные экземплярные свойства и методы принадлежат главному глобальному актору, а доступ из другого изолированного контекста должен быть согласован с правилами Swift Concurrency.
В примере загрузка выполняется вне главного актора, а изменение name — через изолированный метод. await перед updateName не означает обязательно длительную работу: он показывает потенциальный переход между контекстами и возможность приостановки текущей задачи.
Гарантия относится именно к состоянию, изолированному MainActor. Если ProfileStore возвращает наружу обычный изменяемый ссылочный объект, его внутренние данные не становятся защищёнными автоматически. Для такого объекта нужна собственная изоляция: отдельный actor, синхронизация или неизменяемое представление.
MainActor также не превращает блокирующую работу в асинхронную. Долгий синхронный расчёт внутри изолированного метода будет занимать исполнитель главного актора и может заморозить интерфейс. Тяжёлую работу следует выполнять вне него, а на главный актор возвращать только короткое обновление состояния.
Вызов из синхронного не изолированного кода нельзя исправить одним добавлением await: синхронный контекст не может приостановиться для перехода на другой исполнитель. Нужно изменить границу API, например сделать вызывающий путь асинхронным, изолировать его тем же глобальным актором или явно организовать корректную передачу работы.
Экран поиска получает результаты сети. Пользователь быстро вводит новый запрос, пока старый запрос ещё выполняется. Если сетевой слой и состояние экрана не разделены, поздний ответ старого запроса может перезаписать более новый результат.
Можно защищать состояние блокировкой, но это усложняет правила владения и не решает проблему устаревших ответов. Можно отправлять все операции через GCD на главный поток, однако компилятор не получает полноценной информации об изоляции. Практичнее изолировать состояние модели экрана через MainActor, а после получения результата дополнительно проверять идентификатор актуального запроса или использовать отмену задачи.
В результате доступ к состоянию становится проверяемым компилятором и сериализованным, а проверка актуальности решает отдельную проблему логического порядка результатов. Это важное разделение: защита от гонки данных не гарантирует, что бизнес-логика выберет именно самый новый ответ.
Нет, основной языковой контракт — изоляция относительно MainActor, а не обещание конкретной аппаратной нити для каждой инструкции. Реализация главного актора предназначена для работы с главным потоком интерфейса, но корректный вывод на собеседовании должен формулироваться через акторную изоляцию и сериализацию, а не через безусловное утверждение о потоке.
Глобально-акторная изоляция делает передачу ссылки на такой тип между конкурентными контекстами безопаснее: операции над его изолированным состоянием должны возвращаться в соответствующий актор. Но это не превращает все связанные с ним объекты и результаты методов в Sendable. Возвращаемые ссылочные значения и замыкания нужно оценивать отдельно.
Актор сериализует выполнение изолированных участков, но await может приостановить задачу и позволить другой задаче изменить состояние до возобновления первой. Поэтому последовательность из чтения, асинхронного ожидания и последующей записи не является одной неделимой транзакцией. Если решение зависит от ранее прочитанного значения, проверку и изменение нужно объединять в подходящей изолированной операции или повторно проверять состояние после ожидания.