Ответ
Потому что UIKit и SwiftUI не являются потокобезопасными. Все операции с пользовательским интерфейсом должны выполняться на главном потоке (main thread). Нарушение этого правила приводит к:
- Неожиданным крашам (часто с ошибкой
Main Thread Checker). - Визуальным артефактам и задержкам отрисовки.
- Непредсказуемому поведению элементов интерфейса.
Правильный паттерн:
// 1. Выполняем тяжелую работу в фоне
DispatchQueue.global(qos: .userInitiated).async {
let processedImage = self.applyFilters(to: originalImage)
// 2. Возвращаемся на главный поток для обновления UI
DispatchQueue.main.async {
self.imageView.image = processedImage
self.activityIndicator.stopAnimating()
}
}
SwiftUI (автоматическое управление):
Task {
// Автоматически выполняется в фоновом потоке
let data = await fetchData()
// Обновление @Published/@State свойств автоматически
// переносится на главный поток
self.items = data
}
Исключения (но с осторожностью):
- Создание
UIImage(data:)из фонового потока допустимо, но установка вUIImageView— только на главном. - Некоторые вычисления layout (например,
sizeThatFits) могут работать в фоне, если не обращаются к UI-иерархии.
Проверка текущего потока:
assert(Thread.isMainThread, "Этот код должен выполняться на главном потоке!") Ответ 18+ 🔞
А, слушай, вот тебе история про главный поток, такая хуйня, что волосы дыбом встают. Представь себе, ты в своей квартире, всё по полочкам разложено, и тут вдруг какой-то левый мужик с улицы заходит и начинает мебель двигать, картины перевешивать. Что будет? Пиздец и бардак, вот что.
Так вот, UIKit и SwiftUI — это твоя квартира, а главный поток — это ты, единственный хозяин, у которого ключи. Всё, что касается интерфейса — кнопки, картинки, анимации — это священная территория, и лезть туда с грязными сапогами из другого потока — это прямой билет в краш-лэнду с ошибкой Main Thread Checker. Это как нажать на красную кнопку с надписью «Не жми, блядь».
Что будет, если накосячить:
- Краш приложения. Не «ой, ладно», а такой пиздец, что пользователь вылетит из аппа, и доверия ебать ноль.
- Артефакты на экране. Картинка дергается, анимация клинит, будто всё через жопу собрано.
- Непредсказуемость. Кнопка сегодня нажимается, а завтра — хуй с горы, зависла и не отвечает.
Как делать правильно, чтобы не было мучительно больно:
// 1. Всю тяжелую, потную работу — вон, на задний двор, в фон.
DispatchQueue.global(qos: .userInitiated).async {
let processedImage = self.applyFilters(to: originalImage) // Тут фильтры, загрузки, пиздец какой сложный код
// 2. А как результат готов — ОБЯЗАТЕЛЬНО возвращаемся в главную хату, на основной поток!
DispatchQueue.main.async {
self.imageView.image = processedImage // И только тут ставим картинку
self.activityIndicator.stopAnimating() // И крутилку выключаем
}
}
В SwiftUI, кстати, чуть проще, там за тебя думают, хитрая жопа:
Task {
// Эта штука сама уползет в фон, не надо руками пихать
let data = await fetchData()
// А тут, когда данные приползли, SwiftUI сам, как хороший дворник,
// перенесет обновление `self.items` на главный поток. Магия, ёпта.
self.items = data
}
Есть, конечно, исключения, но с ними как с огнем — обожрешься.
Можно, например, создать UIImage из данных в фоне — это просто байты в памяти, похуй. Но вот сунуть эту картинку в UIImageView — это уже работа с интерфейсом, и делать это нужно только на главном! Иначе — в рот меня чих-пых, получишь артефакты или креш.
И на десерт, чтоб совсем спать спокойно:
assert(Thread.isMainThread, "Эй, мудила! Этот код должен выполняться на главном потоке!")
Воткни такую проверку в критичное место, и если кто-то (или ты сам) накосячит — получит по ебалу сразу на этапе отладки. Красиво и практично.