Почему операции с UIStackView должны выполняться на главном потоке?

Ответ

UIStackView, как и любой другой компонент UIKit, не является потокобезопасным. Все манипуляции с его свойствами и иерархией вложенных представлений (addArrangedSubview, removeArrangedSubview, изменение axis, spacing и т.д.) должны выполняться строго на главной потоке (Main Thread).

Причина: UIKit внутренне использует небезопасные для многопоточности структуры данных. Параллельные изменения из нескольких потоков приводят к неопределенному поведению и крашам.

Правильный подход: Все обновления UI должны быть обернуты в вызов DispatchQueue.main.

// Фоновый поток (например, после сетевого запроса)
DispatchQueue.global(qos: .background).async {
    let newView = UIView()
    // ... настройка newView

    // НЕПРАВИЛЬНО - вызовет краш или неопределенное поведение
    // self.stackView.addArrangedSubview(newView)

    // ПРАВИЛЬНО - переход на главный поток
    DispatchQueue.main.async {
        self.stackView.addArrangedSubview(newView)
        // Любые другие обновления UIStackView
    }
}

Как проверить поток? Используйте Thread.isMainThread для отладки.

if Thread.isMainThread {
    stackView.isHidden = true
} else {
    DispatchQueue.main.async { [weak self] in
        self?.stackView.isHidden = true
    }
}

Исключение: Чтение некоторых свойств UI (например, frame, bounds) из фонового потока может быть безопасным, но это поведение не гарантировано документацией. Рекомендуется всегда выполнять любые взаимодействия с UIKit на главном потоке.

Ответ 18+ 🔞

О, смотри-ка, какая интересная тема подъехала! UIStackView и его потоки. Ну что ж, давай разберём эту историю, как говорится, по косточкам.

Слушай, а ты знал, что UIStackView — это такая же нежная и ранимая сущность, как и весь остальной UIKit? Он, блядь, как тот самый Герасим из рассказа — сильный, здоровый, работу делает, но если к нему не с той стороны подойти, может и впендюрить так, что мало не покажется. А подходить к нему нужно строго с главной дороги, то есть с Main Thread.

Представь себе картину: ты в фоновом потоке, такой весь из себя асинхронный и многопоточный, решил добавить в стек новую вьюху. И пишешь:

self.stackView.addArrangedSubview(newView)

А потом — бац! — и приложение накрылось медным тазом. И ты сидишь и думаешь: «Что же я, мудак, сделал?» А сделал ты, чувак, классическую ошибку распиздяя. Ты полез в UI не с того потока.

Вся соль в том, что внутри UIKit — там такие древние и хитрые механизмы, которые просто терпеть не могут, когда к ним лезут одновременно с разных сторон. Это как пытаться переставить мебель в комнате, пока в ней идёт драка. Ничего хорошего не выйдет, только посуда побьётся.

Правильный подход, блядь, один: всё, что связано с внешним видом, делаем на главной очереди. Через DispatchQueue.main.async. Это как сказать: «Эй, главный поток, как освободишься — сделай вот эту хуйню, ладно?»

Вот смотри, как надо:

// Допустим, ты в фоновом потоке, данные пришли
DispatchQueue.global(qos: .background).async {
    let fancyNewView = UILabel()
    fancyNewView.text = "Привет с бэкграунда!"

    // А теперь — стоп, машина! Не лезь сам!
    // self.stackView.addArrangedSubview(fancyNewView) // Это пиздец, не делай так!

    // Вместо этого — вежливо просим главный поток
    DispatchQueue.main.async {
        self.stackView.addArrangedSubview(fancyNewView) // Вот так, бля, правильно!
        // И спейсинг поменять, и axis — всё тут же, в одном месте.
    }
}

А если ты параноик (а в нашем деле это хорошее качество), то можешь и проверку вставить:

if Thread.isMainThread {
    // О, да мы уже дома, на главной! Можно работать.
    stackView.isHidden = true
} else {
    // Опа, мы не там! Вызываем такси до Main Thread.
    DispatchQueue.main.async { [weak self] in
        self?.stackView.isHidden = true
    }
}

И да, запомни раз и навсегда: чтение некоторых свойств (типа frame) из фона иногда может прокатить, но это как ходить по тонкому льду — вроде держит, а потом раз — и ты уже в холодной воде с ошибкой EXC_BAD_ACCESS. Документация тебе этого не гарантирует. Так что не выёбывайся, делай всё по-человечески, на главном потоке.

Короче, мораль проста: хочешь жить спокойно и без крашей — не трогай UIKit из фоновых потоков. Всё, что видит пользователь, должно обновляться там, где этому и положено — в главном потоке. Всё остальное — это путь в никуда, в мир рандомных крешей и невъебенных багов. Не усложняй себе жизнь, делай как надо.