С появлением
Swift Concurrency многие разработчики стали добавлять
actor везде, где видят асинхронность. Это как использовать танк для поездки в магазин за хлебом: мощно, но бессмысленно и создает больше проблем, чем решает.
Вот простой и эффективный чек-лист, который позволит определить, нужно использовать
actor или нет:
У вас есть состояние, которое не является Sendable?
Sendable - это маркер того, что тип безопасен для использования в разных параллельных контекстах. Если все ваши данные (структуры, простые классы) уже соответствуют
Sendable, то первое и важное основание для использования
actor просто отсутствует.
- Пример не Sendable: класс с изменяемым (var) свойством, которое является ссылочным типом (например, другим не-Sendable классом) и не защищен блокировками.
- Пример Sendable: структура (struct) только с let свойствами простых типов (Int, String) или других Sendable типов.
Если у вас либо нет состояния, либо оно уже потокобезопасно (Sendable).
actor для его защиты избыточен.
Операции с этим состоянием должны быть атомарными?
Атомарность означает, что промежуточное состояние объекта никогда не будет видно извне. Либо операция выполнена полностью, либо не выполнена совсем.
- Пример атомарной операции: списание суммы со счета и ее зачисление на другой счет в банковском приложении. Эти два действия должны быть одним неделимым целым.
- Пример не атомарной операции: загрузка данных из сети и их последующее отображение. Это два независимых шага, между которыми система может быть в корректном состоянии.
Если ваши операции можно разбить на независимые шаги или они не требуют такой строгой изоляции, возможно, хватит обычной очереди (DispatchQueue) или
async/await с корректным проектированием.
Эти операции не могут быть выполнены на уже существующем actor (например @MainActor)?
Часто проблема решается не созданием нового изолированного острова actor, а правильным использованием существующих.
- Проверьте: может ли ваша логика быть изолирована к @MainActor, если она касается UI? Или может ее стоит вынести в @concurrent функцию (Swift 6.2+), чтобы просто запустить в параллельном пуле потоков?
- Классическая ошибка: создавать отдельный actor только для того, чтобы выгрузить работу с основного потока. Для этого есть async let, Task.detached или аннотация @concurrent.
Вы не исчерпали возможности существующих механизмов изоляции. Новый
actor добавит накладные расходы на переключение контекстов без реальной необходимости.
Вывод:
Actor - это специальный и достаточно ресурсоемкий механизм, созданный для решения конкретной проблемы: строгой изоляции разделяемого изменяемого состояния. Его не следует применять автоматически для любой фоновой работы.
Прежде чем его использовать, необходимо честно ответить на три ключевых вопроса: есть ли у вас состояние, не являющееся потокобезопасным (Sendable), требуют ли операции с ним атомарности и действительно ли эту работу невозможно выполнить в рамках уже существующего контекста изоляции. Только утвердительные ответы на все три пункта оправдывают введение
actor. Во всех остальных сценариях стоит отдавать предпочтение более легким инструментам, таким как
async/await, проектирование с
Sendable типами, классические
DispatchQueue или новая аннотация
@concurrent.