← Назад к вопросам

Как будешь одновременно отображать данные из трех разных запросов?

2.3 Middle🔥 221 комментариев
#Многопоточность и асинхронность#Работа с сетью

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Стратегии одновременного отображения данных из нескольких запросов

При одновременном отображении данных из трех разных асинхронных запросов в iOS-приложении существует несколько стратегий, выбор которых зависит от требований UI/UX, взаимосвязи данных и необходимости синхронизации.

Основные архитектурные подходы

1. Параллельное выполнение с агрегацией результатов

Наиболее эффективный подход — запустить все три запроса параллельно и дождаться завершения всех, прежде чем обновлять интерфейс.

func loadAllData() async {
    async let firstData = fetchFirstRequest()
    async let secondData = fetchSecondRequest()
    async let thirdData = fetchThirdRequest()
    
    do {
        let (result1, result2,\ result3) = try await (firstData, secondData, thirdData)
        await MainActor.run {
            updateUI(with: result1, result2, result3)
        }
    } catch {
        handleError(error)
    }
}

2. Использование DispatchGroup для синхронизации

Классический подход, особенно полезный при работе с completion handlers:

func loadDataWithDispatchGroup() {
    let group = DispatchGroup()
    var results: (Data1?, Data2?, Data3?) = (nil, nil, nil)
    
    group.enter()
    fetchFirstRequest { data in
        results.0 = data
        group.leave()
    }
    
    group.enter()
    fetchSecondRequest { data in
        results.1 = data
        group.leave()
    }
    
    group.enter()
    fetchThirdRequest { data in
        results.2 = data
        group.leave()
    }
    
    group.notify(queue: .main) {
        guard let data1 = results.0,
              let data2 = results.1,
              let data3 = results.2 else {
            // Обработка частичных ошибок
            return
        }
        self.updateUI(data1, data2, data3)
    }
}

3. Combine Framework для реактивного подхода

Для современных SwiftUI-приложений или MVVM-архитектуры идеально подходит Combine:

class DataViewModel: ObservableObject {
    @Published var combinedData: CombinedModel?
    private var cancellables = Set<AnyCancellable>()
    
    func loadData() {
        let publisher1 = fetchFirstPublisher()
        let publisher2 = fetchSecondPublisher()
        let publisher3 = fetchThirdPublisher()
        
        Publishers.Zip3(publisher1, publisher2, publisher3)
            .receive(on: DispatchQueue.main)
            .sink { completion in
                if case .failure(let error) = completion {
                    self.handleError(error)
                }
            } receiveValue: { (data1, data2, data3) in
                self.combinedData = CombinedModel(data1, data2, data3)
            }
            .store(in: &cancellables)
    }
}

Управление состоянием интерфейса

Прогрессивное отображение данных

Если данные могут загружаться с разной скоростью, можно использовать инкрементальное обновление UI:

class ProgressiveLoader {
    @Published var firstData: DataType?
    @Published var secondData: DataType?
    @Published var thirdData: DataType?
    @Published var isLoading = true
    
    private let serialQueue = DispatchQueue(label: "data.merging.queue")
    private var loadedCount = 0
    
    func loadData() {
        // Каждый запрос при завершении инкрементально обновляет состояние
        fetchFirstRequest { [weak self] data in
            self?.serialQueue.async {
                self?.firstData = data
                self?.checkCompletion()
            }
        }
        // Аналогично для второго и третьего запросов
    }
    
    private func checkCompletion() {
        loadedCount += 1
        if loadedCount == 3 {
            DispatchQueue.main.async {
                self.isLoading = false
                self.finalizeUI()
            }
        }
    }
}

Обработка ошибок и edge cases

Стратегии восстановления:

  • Частичное отображение: Показывать успешно загруженные данные с индикацией ошибки для неудавшихся запросов
  • Retry логика: Автоматический повтор неудачных запросов с экспоненциальной задержкой
  • Fallback данные: Использование кэшированных или дефолтных значений при ошибках
enum DataLoadingState {
    case loading
    case partialSuccess(data1: Data1?, data2: Data2?, data3: Data3?, errors: [Error])
    case success(data1: Data1, data2: Data2, data3: Data3)
    case failure(Error)
}

// В UI слое
switch loadingState {
case .partialSuccess(let data1, let data2, let data3, let errors):
    showPartialData(data1, data2, data3)
    showErrorBanners(for: errors)
// ... другие cases
}

Практические рекомендации

  1. Приоритизация запросов: Определите, какие данные критичны для первичного отображения
  2. Оптимизация производительности:
    • Установите разумные таймауты для каждого запроса
    • Используйте приоритетные очереди для критичных данных
    • Реализуйте отмену запросов при уходе со screen
  3. Тестирование:
    • Симулируйте различные скорости ответов от серверов
    • Тестируйте сценарии частичных успехов/неудач
    • Проверяйте поведение при плохом соединении

Заключение

Выбор конкретной стратегии зависит от контекста: для SwiftUI приложений оптимален Combine, для UIKit с поддержкой iOS 13+ — async/await, для поддержки старых версий — DispatchGroup. Ключевые принципы: максимальный параллелизм, грамотная обработка ошибок, плавное обновление UI и эффективное использование системных ресурсов. Всегда учитывайте user experience — иногда лучше показать часть данных сразу, чем заставлять пользователя ждать полной загрузки всех трёх источников.