В Swift, нужно ли явно переключаться на главный поток (MainActor) после вызова async-метода внутри Task в viewDidLoad?

«В Swift, нужно ли явно переключаться на главный поток (MainActor) после вызова async-метода внутри Task в viewDidLoad?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Ответ зависит от контекста выполнения async-метода.

1. Когда переключение НЕ нужно: Если async-метод или вызывающий его тип помечен аннотацией @MainActor, то весь код внутри этого метода и последующие вызовы уже выполняются на главном потоке.

@MainActor
func fetchUserData() async -> UserData { ... }

override func viewDidLoad() {
    super.viewDidLoad()
    Task {
        let data = await fetchUserData() // Выполняется на MainActor
        updateUI(with: data) // Безопасное обновление UI
    }
}

2. Когда переключение НУЖНО: Если async-метод не изолирован на MainActor (например, выполняет сетевой запрос или тяжелые вычисления), то для обновления UI необходимо явно переключиться.

// Этот метод не помечен @MainActor
func fetchFromNetwork() async -> Data { ... }

override func viewDidLoad() {
    super.viewDidLoad()
    Task {
        let rawData = await fetchFromNetwork() // Может выполняться на фоновом потоке
        // ОШИБКА: Прямое обновление UI здесь небезопасно
        // self.label.text = String(data: rawData)

        // ПРАВИЛЬНО: Явное переключение
        await MainActor.run {
            self.updateUI(with: rawData) // Теперь на главном потоке
        }
    }
}

Практическое правило:

  • Проверяй, помечен ли вызываемый метод или его класс как @MainActor.
  • Любое изменение свойств UIKit/SwiftUI должно происходить на главном потоке.
  • Используй await MainActor.run { ... } для явного переключения контекста, если не уверен.
  • Современный подход — аннотировать методы, обновляющие UI, как @MainActor, чтобы компилятор контролировал потоковую безопасность.