Swift — Возникли проблемы с возвратом нескольких сетевых вызовов в том порядке, в котором они были выполнены с использованием gcd

#swift

#быстрый #swift

Вопрос:

Я изучаю swift и решил создать простое приложение для погоды. У меня возникла проблема с возвратом данных о погоде в том порядке, в котором я их запрашивал. В моем контроллере просмотра у меня есть 2 свойства: WeatherData и locations, которые оба являются массивами. Когда приложение открыто, я загружаю все сохраненные местоположения из UserDefaults. Затем я перебираю каждое местоположение и выполняю несколько сетевых вызовов для получения данных о погоде для каждого местоположения. Я использую группы отправки, чтобы убедиться, что я жду, пока все данные не будут загружены, прежде чем использовать данные о погоде для загрузки ViewModels, которые их используют. Проблема в том, что данные о погоде не всегда возвращаются в том порядке, в котором я их запрашивал, поэтому индексы местоположения и WeatherData не выровнены / синхронизированы. Я думал, что использование DispatchQueue.main.async будет последовательным и выполнит элементы по порядку, но, похоже, этого не происходит. Я, очевидно, неправильно понимаю, как работает gcd, и действительно изо всех сил пытаюсь найти решение для этого. Любые рекомендации были бы высоко оценены, заранее спасибо!

Код в ViewController

 private func updateWeatherData() {
    let myGroup = DispatchGroup()


    for i in 0..<locations.count {
        myGroup.enter()
        print("Calling fetch Weather for index: ", i )
        self.apiService.fetchWeatherData(for: self.locations[i].location) { (response, error) in
            if let error = error {
                // Unable to retrieve weather data
                self.presentAlert()
            } else if let response = response {
                print("Finished Request: (i)")
                self.weatherData.append(response)

            }
            myGroup.leave()
        }
    }

    myGroup.notify(queue: .main) {
        print("Finished All requests")
        self.locationsTableViewController.viewModel = LocationsViewModel(locations: self.locations, weatherData: self.weatherData)
    }
}
  

Код в моем классе apiService

 func fetchWeatherData(for location: CLLocation, completion: @escaping WeatherDataCompletion) {
        // Weather request used to return valid api url string based on location 
        let weatherRequest = WeatherRequest(baseUrl: APIConfiguration.authenticatedBaseUrl, location: location)

        URLSession.shared.dataTask(with: weatherRequest.url) { (data, response, error) in
            DispatchQueue.main.async {
                self.didFetchWeatherData(data: data, response: response, error: error, completion: completion)

            }
        }.resume()
    }


    // Error checks and decodes network data response
    private func didFetchWeatherData(data: Data?, response: URLResponse?, error: Error?, completion: WeatherDataCompletion) {
        if let error = error {
            completion(nil, .failedRequest)
            print("No WeatherData Available: ", error)
        } else if let data = data, let response = response as? HTTPURLResponse {
            if response.statusCode == 200 {
                do {
                    // Decode JSON
                    let weatherData = try decoder.decode(DarkSkyResponse.self, from: data)
                    completion(weatherData, nil)
                } catch {
                    print("Unable to Decode JSON: ", error)
                    completion(nil, .invalidResponse)
                }
            } else {
                print("Status Code: ", response.statusCode)
                completion(nil, .failedRequest)
            }
        } else {
            completion(nil, .unknown)
        }
    }
  

РЕДАКТИРОВАТЬ: Как упоминалось в ответе, я узнал, что URLSession не гарантирует порядок. В итоге я просто сохранил каждый результат по индексу в начальном цикле.

Ответ №1:

С помощью этого

 URLSession.shared.dataTask(with: weatherRequest.url)
  

нет гарантии, что вы получите ответы на запросы по порядку, поэтому вы можете создать модель, которая содержит запрос с некоторым свойством number , а затем после завершения всех ответов здесь

 myGroup.notify(queue: .main) { }
  

отсортируйте их по номеру или сделайте это

 class LocationsViewModel {
   var loc:CLLocation
   var weatherData:DarkSkyResponse?
   init(loca:CLLocation) {
     self.loc = loca
   } 
   func fetchWeatherData(completion: @escaping WeatherDataCompletion) {
    // here do the fetch and assign the weatherData
     weatherData = decodeRes 

   }
} 
  

еще одна вещь, которую вы также можете связать запросы таким образом, чтобы после возврата ответа выполнить следующий вызов и так далее, Также вам следует удалить

 DispatchQueue.main.async {
  

поскольку все должно происходить в фоновом потоке