Как избежать потери порядка при записи результатов асинхронных задач в массив в iOS
Что проверяют
Понимаете ли вы, что асинхронные задачи завершаются не в том порядке, в каком запущены, и что append в общий массив из разных потоков это гонка данных, а не просто перепутанный порядок. Следом обычно просят переписать решение на Swift Concurrency.
Как отвечать
Сначала назовите две проблемы отдельно: порядок завершения случаен, а Array в Swift не потокобезопасен, и одновременный append может потерять элемент или уронить приложение. Дальше способы:
- запись по индексу: заранее создать
[Value?](repeating: nil, count: n)и писатьresults[i] = valueна одной серийной очереди, аDispatchGroupсenter()/leave()иnotify(queue: .main)сообщит, что всё готово; - TaskGroup:
withTaskGroup(of: (Int, Value).self), каждая задача возвращает пару «индекс, результат».for awaitотдаёт их в порядке завершения, поэтому раскладывать нужно по индексу, а не дописывать в конец; - async let: для двух-трёх фиксированных запросов порядок задаёт сам код:
let (a, b) = await (first, second); - actor: если результаты копятся в общем хранилище из разных задач, изоляция actor убирает гонку без ручных очередей;
- последовательный
awaitв цикле: когда порядок важнее скорости, но тогда вы сознательно отказываетесь от параллельности.
Хорошо добавить, что в языковом режиме Swift 6 изменение захваченного var из конкурентного замыкания компилятор отклоняет, то есть часть таких ошибок ловится ещё до запуска.
Где спотыкаются
Показывают DispatchGroup с append внутри колбэка и говорят «порядок сохранён». Группа только ждёт завершения всех задач, порядок и потокобезопасность она не даёт.
Забывают про ветку ошибки: если в одном из путей колбэка не вызван leave(), notify не сработает никогда, и экран висит в загрузке.
Считают, что for await в TaskGroup возвращает результаты в порядке добавления задач. Он отдаёт их по мере готовности, и без индекса порядок снова теряется.