Ответ
Основное правило: UI-компоненты UIKit/AppKit можно обновлять только в главном потоке, так как они не потокобезопасны.
Исключение: Метод drawHierarchy(in:afterScreenUpdates:) можно вызывать в фоновом потоке для рендеринга в изображение, например при создании скриншотов.
Пример легального использования в фоновом потоке:
DispatchQueue.global(qos: .background).async {
let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
let screenshot = renderer.image { context in
// Это безопасно в фоновом потоке
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
}
// Далее работаем с изображением (сохранение, обработка)
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
}
Стандартный подход для UI-обновлений:
// Всегда используйте DispatchQueue.main для UI изменений
DispatchQueue.main.async {
self.label.text = "Новый текст"
self.button.isEnabled = false
self.view.backgroundColor = .systemBlue
}
Важно: SwiftUI требует обновления состояния также в главном потоке через @MainActor или DispatchQueue.main.
Ответ 18+ 🔞
Ага, слушай, вот сидишь ты такой, пишешь свой код, и тут — бац! — нужно UI обновить. И ты такой: "Да похуй, сделаю в бэкграунде". А потом — охуенно! — приложение крешится, и ты сидишь, чешешь репу: "Ну что за хуйня?"
Так вот, запомни раз и навсегда, как отче наш: все эти ваши UIKit/AppKit штуки — они, блядь, не потокобезопасные. Совсем. Вообще. Нихуя. Обновлять их можно только в главном потоке. Это как закон природы, типа гравитации. Попробуешь нарушить — получишь ебучую ошибку и креш в самом интересном месте.
Но! Как и в любом правиле, тут есть одна хитрая жопа, одно исключение. Метод drawHierarchy(in:afterScreenUpdates:). Его, сука, можно вызывать в фоновом потоке, но только для одной цели — чтобы нарисовать вьюху в картинку. Например, когда тебе скриншот нахуйрить нужно. Вот так:
DispatchQueue.global(qos: .background).async {
let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
let screenshot = renderer.image { context in
// Это безопасно в фоновом потоке
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
}
// Далее работаем с изображением (сохранение, обработка)
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
}
Видишь? Всё чётко. Рендерим в фоне, получаем UIImage, и потом уже с ним делаем что хотим. А вот лезть из фонового потока и пытаться поменять text у лейбла или backgroundColor — это прямой билет в ад, чувак. Там тебя ждёт неопределённое поведение, гонки данных и прочий пиздец.
Поэтому для обычных UI-апдейтов — только так, блядь:
// Всегда используйте DispatchQueue.main для UI изменений
DispatchQueue.main.async {
self.label.text = "Новый текст"
self.button.isEnabled = false
self.view.backgroundColor = .systemBlue
}
Вот это — святое. Запомнил? И да, это касается не только старого доброго UIKit. Твой модный SwiftUI тоже, ёпта, требует, чтобы состояние обновлялось в главном потоке. Через @MainActor или тот же DispatchQueue.main. Не выёбывайся, делай как все умные люди.
Короче, правило простое: думаешь потрогать UI — сначала спроси себя: "А я в главном потоке?" Если нет — оберни в DispatchQueue.main.async и живи спокойно. Иначе будешь потом ночами дебажить, почему всё ебётся.