Можно ли обновлять UITableView в замыкании URLSession после получения ответа и почему
Что проверяют
Знаете ли вы, на какой очереди приходит ответ сети и почему UIKit трогают только с главного потока. Следом часто просят переписать то же на async/await или спрашивают, что будет, если про главный поток забыть.
Как отвечать
Напрямую из замыкания нельзя, через переход на главную очередь можно. Порядок рассуждения:
- где выполняется замыкание: completion handler у
dataTaskвызывается наdelegateQueueсессии. УURLSession.sharedэто фоновая последовательнаяOperationQueue, не главная; - почему нельзя: UIKit не потокобезопасен. Вызов
reloadData()с фона даёт то задержку отрисовки на секунды, то падение, то испорченную раскладку ячеек. Xcode ловит такое через Main Thread Checker, в отладочной схеме он включён по умолчанию; - как правильно: парсинг оставить на фоне, а присвоение массива и
reloadData()обернуть вDispatchQueue.main.async, с[weak self], чтобы запрос не держал закрытый экран; - современный вариант:
let (data, _) = try await URLSession.shared.data(from: url)внутриTaskв контроллере.UIViewControllerпомечен@MainActor, поэтому послеawaitкод продолжится на главном потоке без ручного переключения; - Swift 6: в строгом режиме параллелизма обращение к
tableViewиз фонового замыкания становится ошибкой компиляции, а не сюрпризом в рантайме.
Можно добавить вариант с сессией, созданной с delegateQueue: .main: колбэк придёт сразу на главную очередь, но тяжёлый разбор ответа туда лучше не тащить.
Где спотыкаются
Меняют массив данных на фоне, а reloadData() зовут на главном. Таблица в этот момент может читать массив в промежуточном состоянии, итог: выход за границы в cellForRowAt и падение.
Уходят на главный поток целиком, включая декодирование JSON. Большой ответ, разобранный на главной очереди, даёт заметные подёргивания прокрутки.
Путают DispatchQueue.main.async и sync. Вызов main.sync с главного потока это взаимоблокировка, которую libdispatch обрывает падением приложения.