Какие причины крашей и некорректных данных при использовании пагинации и сокетов в таблице iOS приложения
Что проверяют
Понимаете ли вы, что почти все такие краши происходят из-за рассинхрона модели данных и того, что о ней думает таблица. Следом часто спрашивают, как бы вы перестроили экран, чтобы это исключить.
Как отвечать
Назовите основные причины:
- несовпадение счётчиков: после
insertRowsилиperformBatchUpdatesчисло строк изnumberOfRowsInSectionне сходится с ожидаемым. Итог:NSInternalInconsistencyException; - изменение массива не на главном потоке: ответ сети или сокета пишет в массив из фоновой очереди, пока таблица его читает. Отсюда
Index out of rangeи падения внутри анимации; - гонка страницы и сокета: страница, сформированная до события, приходит позже и затирает свежие данные. Или событие касается элемента, которого ещё нет в загруженном списке;
- offset-пагинация на живых данных: сокет вставил запись в начало, позиции сдвинулись, и следующая страница начинается с уже показанной строки;
- дубли в diffable data source:
UITableViewDiffableDataSourceпадает, если в снапшоте повторяются идентификаторы; - повторная загрузка страницы:
willDisplayили prefetch срабатывает несколько раз, и без флагаisLoadingодна страница приходит дважды; - переиспользование ячеек: асинхронная картинка или сохранённый
indexPathприходят в ячейку, которая уже показывает другую строку.
Решение строится вокруг одного источника правды. Все изменения модели идут последовательно на @MainActor или через один актор, который отдаёт готовый снапшот. Список дедуплицируется по id, пагинация идёт по курсору, у событий сокета есть версия или время, и устаревшие отбрасываются. События, пришедшие во время загрузки страницы, буферизуются и применяются после неё. После переподключения сокета список дозапрашивается.
Где спотыкаются
Лечат краш вызовом reloadData() вместо батч-обновлений. Падать перестаёт, но гонка остаётся, и данные всё так же бывают неверными.
Обновляют массив в фоне, а на главный поток переходят только ради reloadData. Таблица успевает прочитать массив в промежуточном состоянии.
Забывают про переподключение: пока сокет был отключён, события пропали, а экран об этом не знает.