Какие UI операции можно делать не на главном потоке?ios-89

Основное правило UIKit

Главное правило: Все операции, изменяющие визуальное состояние UI, должны выполняться на главном потоке. UIKit не является потокобезопасным.

Что категорически нельзя делать вне главного потока

  1. Изменение любых свойств UIView/UIViewController
  2. Создание новых экземпляров UIView/UIViewController
  3. Работа с CALayer, связанная с отображением
  4. Обработка событий пользовательского ввода
  5. Вызов методов, влияющих на layout (setNeedsLayout, layoutIfNeeded)
// ОПАСНО: Это вызовет проблемы
DispatchQueue.global().async {
    self.label.text = "New Text" // CRASH или глюки интерфейса
    self.view.backgroundColor = .red // Тоже опасно
}

Что МОЖНО делать вне главного потока

1. Подготовка данных для UI

DispatchQueue.global(qos: .userInitiated).async {
    let image = self.processImage(rawImage) // Ресайз/фильтрация

    DispatchQueue.main.async {
        self.imageView.image = image // Установка только на main
    }
}

2. Чтение некоторых UI-свойств

DispatchQueue.global().async {
    let currentText = self.label.text // Чтение обычно безопасно
    let currentSize = self.view.frame.size // Но может быть устаревшим

    // Важно: frame может измениться на main потоке!
}

3. Работа с UIImage

// Создание UIImage из данных - безопасно
DispatchQueue.global().async {
    if let image = UIImage(data: rawImageData) {
        DispatchQueue.main.async {
            self.imageView.image = image
        }
    }
}

4. Предварительный расчет layout

DispatchQueue.global().async {
    let size = text.calculateSize(for: maxWidth: 300) // Кастомный расчет

    DispatchQueue.main.async {
        self.label.frame.size = size // Применяем на main
    }
}

5. Работа с Core Graphics

DispatchQueue.global().async {
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    defer { UIGraphicsEndImageContext() }

    // Рисование в контексте
    let image = UIGraphicsGetImageFromCurrentImageContext()

    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

Современные подходы

1. Использование async/await

Task {
    let processedImage = await processImageAsync(rawImage)

    await MainActor.run {
        self.imageView.image = processedImage
    }
}

2. MainActor

Метки для автоматического выполнения на главном потоке:

@MainActor
func updateUI(with data: Data) {
    self.label.text = data.text // Гарантированно на main
}

Опасные исключения

  1. UIFont: Создание шрифтов технически безопасно вне main, но может привести к проблемам производительности
  2. UIColor: Создание цветов обычно безопасно, но модификация существующих - нет
  3. NSAttributedString: Создание возможно, но применение к UILabel - только на main

Практические рекомендации

  1. Всегда используйте DispatchQueue.main.async для UI-обновлений
  2. Для сложных вычислений используйте DispatchQueue.global(qos: .userInitiated)
  3. В Swift 5.5+ предпочитайте MainActor и async/await
  4. При сомнениях - проверяйте текущий поток:
assert(Thread.isMainThread, "Этот метод должен вызываться на главном потоке")

Резюмируем:

несмотря на то, что некоторые подготовительные операции можно выполнять вне главного потока, все изменения, влияющие на отображение UI, должны выполняться строго на главном потоке. Современные механизмы (MainActor, async/await) упрощают соблюдение этого правила.