Ответ
Нет, не нужен. Оператор receive(on:) в Combine явно указывает, на какой очереди (DispatchQueue) будут обрабатываться все последующие события (значения, ошибки, завершение). Если указан DispatchQueue.main, то весь код внутри подписчика (sink, assign) уже выполняется на главной очереди.
Корректный пример:
networkPublisher
.decode(type: User.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main) // Все дальнейшие операции — на главной очереди
.sink { completion in
// Обработка завершения — на главной очереди
} receiveValue: { user in
// Безопасное обновление UI, например:
self.usernameLabel.text = user.name
// Дополнительный DispatchQueue.main.async здесь избыточен.
}
Исключение: Если внутри receiveValue выполняется тяжёлая синхронная операция (например, обработка большого изображения), её следует вынести в фоновую очередь, чтобы не блокировать UI. Но это касается логики работы, а не доставки событий от Combine.