Гарантирует ли атрибут `@MainActor` для класса или ViewModel, что весь его код выполнится на главном потоке?

«Гарантирует ли атрибут `@MainActor` для класса или ViewModel, что весь его код выполнится на главном потоке?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет, не весь код автоматически выполнится на главном потоке. @MainActor гарантирует, что вход в его неизолированные (nonisolated) свойства и методы будет происходить на главном потоке. Однако, внутри async-метода поток может смениться после точки await.

Проблема и решение:

@MainActor
class MyViewModel {
    var data: [String] = []

    func fetchAndUpdate() async {
        // 1. Эта точка выполняется на главном потоке (гарантия @MainActor).

        let newData = await networkService.fetchData() // 2. `await` -> приостановка.
        // 3. После возобновления поток МОЖЕТ БЫТЬ ЛЮБЫМ (например, фоновый).

        // ОПАСНО: Модификация состояния не на главном потоке.
        self.data = newData // Возможна гонка данных и краш UI.

        // РЕШЕНИЕ: Явное возвращение на главный актор.
        await MainActor.run {
            self.data = newData // Теперь безопасно.
        }
    }
}

Правила и лучшие практики:

  1. Гарантируется: Синхронный код и код до первого await в методе @MainActor-типа.
  2. Не гарантируется: Код после await. Исполнитель (Executor) может возобновить выполнение на другом потоке.
  3. Что делать: Все операции, связанные с обновлением состояния (@Published, @State, UI-методы), которые идут после await, нужно оборачивать в await MainActor.run { ... }.

Вывод: @MainActor обеспечивает изоляцию, но в асинхронном контексте ответственность за выполнение UI-кода на главном потоке после await лежит на разработчике.