Как сохранить порядок элементов при асинхронной загрузке
Что проверяют
Умеете ли вы грузить элементы параллельно и собрать результат в исходном порядке без гонки данных. Следом часто спрашивают про ячейки: как не показать картинку в чужой ячейке после переиспользования.
Как отвечать
Главная мысль: порядок задаёт не время прихода ответа, а ключ. Каждая загрузка знает свой индекс или идентификатор, и результат кладётся по нему.
- task group:
withThrowingTaskGroupзапускает загрузки параллельно, каждая возвращает пару(index, data), а родитель в циклеfor try awaitраскладывает их в заранее созданный массив. Запись идёт в одном месте, поэтому гонки нет; - ограничение параллелизма: на сотне URL сначала добавляют в группу, скажем, шесть задач, а новую
addTaskвызывают по мере завершения предыдущих; - ошибки отдельных элементов: если ошибка вылетит из группы, остальные задачи будут отменены. Когда нужны частичные результаты, задача возвращает
Result, а не бросает; - вариант на GCD: массив с
nil-заглушками плюсDispatchGroup, но запись в массив синхронизируют последовательной очередью или замком. В Swift 6 запись в захваченнуюvarиз@Sendable-замыкания компилятор не пропустит.
В списках порядок держит модель, а не сеть. В UICollectionView с diffable data source ячейки создаются сразу по идентификаторам со скелетоном, а пришедшие данные обновляют снапшот через reconfigureItems. В SwiftUI загрузку вешают на .task(id:), которая отменяется при смене идентификатора.
При постраничной загрузке храните номер страницы и отбрасывайте неактуальный ответ, иначе вторая страница, пришедшая раньше первой, окажется сверху.
Где спотыкаются
Делают append в колбэке. Элементы выстраиваются в порядке прихода ответов, а на быстром Wi-Fi баг не воспроизводится.
Пишут в общий массив из нескольких колбэков без синхронизации. Это гонка данных, которая изредка роняет приложение, а Thread Sanitizer находит её сразу.
Забывают про переиспользование ячеек. Картинка догружается и попадает в ячейку, которая уже показывает другой элемент. Задачу нужно отменять в prepareForReuse или сверять идентификатор перед установкой.