За счёт какого механизма экземпляр типа, изолированного глобальным актором, можно передавать между конкурентными задачами без собственной блокировки?
Тип, изолированный глобальным актором, может безопасно передаваться между задачами, потому что доступ к его изолированным экземплярам и состоянию сериализуется этим актором. Такой тип рассматривается как безопасный для передачи между задачами, но это не делает автоматически безопасными его nonisolated-члены или объекты, на которые он ссылается.
Глобальные акторы появились как часть модели конкурентности Swift для случаев, когда состояние должно обслуживаться одним логическим исполнителем: например, пользовательский интерфейс — главным актором. Это решает проблему ручной передачи состояния через очереди и разрозненные блокировки.
Вместо требования передавать копии данных или самостоятельно синхронизировать общий объект Swift связывает доступ к его изолированным членам с конкретным глобальным актором. Поэтому ссылка на объект может пересекать границу задач, а операции над его состоянием выполняются через акторную изоляцию.
Обычный ссылочный тип нельзя считать безопасным только потому, что ссылка передаётся между задачами: разные задачи могут одновременно изменить один объект. Это создаёт гонки данных и неопределённые результаты.
У типа, изолированного глобальным актором, чтение и изменение изолированных членов направляется к одному актору. Однако изоляция защищает именно такие обращения. Если вернуть наружу изменяемый внутренний ссылочный объект или объявить член nonisolated, защита может быть потеряна.
Глобальный актор задаёт единый контекст изоляции для экземпляра и его изолированных членов. Когда задача обращается к такому члену из другого контекста, Swift выполняет асинхронный переход к соответствующему актору; это предотвращает одновременное выполнение изолированного кода на разных задачах.
Тип, изолированный глобальным актором, может пересекать границы конкурентности без отдельной пользовательской реализации Sendable: безопасность передачи обеспечивается тем, что последующая работа с его изолированным состоянием будет сериализована глобальным актором. Это не означает, что вызов синхронной последовательности операций становится одной неделимой транзакцией: на await выполнение может приостановиться, а состояние — измениться до продолжения.
В примере Counter изолирован главным актором и передаётся дочерним задачам без собственной блокировки. Каждый вызов increment выполняется на главном акторе, поэтому два изменения value не происходят одновременно.
Есть важные ограничения. nonisolated-член не выполняется под защитой глобального актора, а передача внутреннего изменяемого объекта наружу может позволить обходить изоляцию. Кроме того, глобальный актор обеспечивает сериализацию доступа, но может стать узким местом, если на нём выполняются тяжёлые или блокирующие операции.
Команда хранит состояние экрана в классе, помеченном @MainActor, а сетевой слой запускает несколько задач, обновляющих это состояние. Вариант с общей обычной ссылкой и ручной блокировкой требует дисциплины: каждый доступ должен использовать одну и ту же блокировку, иначе защита легко нарушается.
Вариант с отдельным actor хорошо защищает состояние, но требует явно организовать взаимодействие между ним и главным актором. Для состояния, которое по смыслу принадлежит UI, это добавляет ненужный слой переходов.
Выбранный вариант — изолировать UI-модель @MainActor и передавать её в дочерние задачи. Сетевые задачи не изменяют её напрямую: они получают данные, а короткие операции обновления выполняются через главный актор. Это сохраняет безопасность данных и не переносит сетевую работу на главный исполнитель.
1. Делает ли глобальный актор любую операцию над объектом атомарной?
Нет. Отдельный изолированный вызов сериализован, но последовательность из нескольких вызовов может быть прервана await, а между ними другая задача успеет изменить состояние. Для составной операции её нужно целиком разместить в одном изолированном методе или использовать другой механизм согласования.
2. Защищает ли глобальный актор объект, возвращённый из его метода?
Нет, не автоматически. Если изолированный объект возвращает изменяемый ссылочный объект, вызывающий код может сохранить ссылку и обращаться к нему в обход исходного глобального актора. Безопаснее возвращать значения, изолировать сам возвращаемый объект тем же или другим актором либо предоставить контролируемые операции вместо утечки внутреннего состояния.
3. Что изменится у члена, объявленного nonisolated в глобально изолированном типе?
Такой член не требует перехода к глобальному актору и не получает его сериализацию. Он должен быть безопасен самостоятельно: например, работать только с неизменяемыми данными или с синхронизированным состоянием. Объявление nonisolated ускоряет доступ и позволяет вызывать его из большего числа контекстов, но одновременно снимает защиту глобального актора.