#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 {
поскольку все должно происходить в фоновом потоке