Как гарантировать вызов completion в очереди оригинального сервиса в iOS
Что проверяют
Понимаете ли вы, что колбэк асинхронного API приходит в чужой очереди, и умеете ли вернуть его туда, где сервис ждёт результат. Часто это сервис со своей приватной последовательной очередью, которая защищает его состояние. Следом спрашивают, почему нельзя просто узнать текущую очередь и что меняет Swift Concurrency.
Как отвечать
В GCD нет надёжного способа спросить «в какой я очереди»: dispatch_get_current_queue давно объявлен устаревшим, а Thread.current говорит о потоке, не об очереди. Поэтому очередь не угадывают, а фиксируют явно:
- очередь в параметре: метод принимает
callbackQueue: DispatchQueueсо значением по умолчанию, например.main, и вызываетcallbackQueue.async { completion(result) }. Так устроены многие сетевые библиотеки; - своя очередь сервиса: сервис держит
private let queue = DispatchQueue(label: ...), и любой результат изURLSessionили другого API сначала переводится в неё черезqueue.async; - очередь делегата:
URLSessionпринимаетdelegateQueue, и колбэки сразу приходят в нужнуюOperationQueue; - проверка инварианта:
dispatchPrecondition(condition: .onQueue(queue))ловит вызов не из той очереди.
Отдельно скажите про однократность: completion вызывается ровно один раз на каждом пути, включая ошибку и отмену. Помогает defer или единая точка выхода.
В современном коде ту же задачу решают акторы: сервис объявляется actor, его состояние изолировано, а async метод возвращает результат вызывающему без ручных переходов между очередями. Для обёртки старого API есть withCheckedContinuation: она аварийно останавливает программу при повторном возобновлении и предупреждает в логе, если continuation потерян.
Где спотыкаются
Всегда вызывают completion через DispatchQueue.main.async. Главная очередь не равна исходной, и сервис, который защищает состояние своей очередью, получает гонку данных.
Переходят в очередь сервиса через sync, уже находясь в ней. На последовательной очереди это мгновенная взаимоблокировка.
Теряют вызов completion в ветке ошибки или вызывают его дважды. Первое подвешивает вызывающий код, второе ломает DispatchGroup и continuation.