Ответ
Баг: Race condition при обновлении UITableView/UICollectionView из фонового потока.
Сценарий: Данные изменяются асинхронно (например, при загрузке из сети), а затем интерфейс обновляется вызовом reloadData() на главном потоке. Если изменения данных и обновление UI не синхронизированы, возникает состояние гонки.
Проблемный код:
// ФОНТОВЫЙ ПОТОК: данные изменяются
DispatchQueue.global().async {
let newData = self.fetchDataFromNetwork()
self.dataArray = newData // ОПЕРАЦИЯ ЗАПИСИ (A)
// ГЛАВНЫЙ ПОТОК: UI обновляется
DispatchQueue.main.async {
self.tableView.reloadData() // ОПЕРАЦИЯ ЧТЕНИЯ (B) dataArray
}
}
Проблема: Нет гарантии, что операция записи (A) завершится до операции чтения (B). UI может прочитать частично обновленные или неконсистентные данные, что приведет к крешам (NSInternalInconsistencyException), некорректному отображению или пропущенным ячейкам.
Решение: Синхронизация доступа к данным.
-
Использование последовательной (serial) очереди:
private let dataQueue = DispatchQueue(label: "com.app.dataQueue") func updateData() { dataQueue.async { let newData = self.fetchDataFromNetwork() self.dataArray = newData DispatchQueue.main.async { self.tableView.reloadData() } } } -
Использование акторов (Actor) в Swift 5.5+:
actor DataModel { private var dataArray: [Item] = [] func update(with newData: [Item]) { dataArray = newData } func getData() -> [Item] { return dataArray } } // Использование Task { let newData = await fetchDataFromNetwork() await model.update(with: newData) await MainActor.run { tableView.reloadData() } }
Почему это важно: UI-компоненты UIKit не потокобезопасны. Все операции с данными, которые отображаются или изменяют UI, должны выполняться на главном потоке (MainActor).