I want to execute "getWeatherInformation" function when "getLocation" function ends with save lat/lon
Running below code, "getWeatherInformation" starts before "getLocation" function saves lat/lon.
I thought it would run synchronously using sync in "DispatchQueue.main.sync", but it wasn't.
How can I solve this? Thank You.
// get lat/lon of cityName
func getLocation(cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&appid=\(keyValue)") else {return}
let session = URLSession(configuration: .default)
session.dataTask(with: url) { [weak self] data, response, error in
let successRange = (200..<300)
guard let data = data, error == nil else {return}
let decoder = JSONDecoder()
if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
guard let location = try? decoder.decode(Location.self, from: data) else {return}
DispatchQueue.main.sync {
self?.findLocation(location: location) // save lat&lon
}
} else {
print("error")
}
}.resume()
}
// get weatherInformation using lat/lon
func getWeatherInformation(lat: Double, lon: Double) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/onecall?lat=\(lat)&lon=\(lon)&exclude=minutely&appid=\(keyValue)") else {return}
let session = URLSession(configuration: .default)
session.dataTask(with: url) { [weak self] data, response, error in
let successRange = (200..<300)
guard let data = data, error == nil else {return}
let decoder = JSONDecoder()
if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
guard let weatherInformation = try? decoder.decode(WeatherInformation.self, from: data) else {return}
DispatchQueue.main.sync {
self?.configureWeather(weatherInformation: weatherInformation)
self?.hourlyCollectionView.reloadData()
}
} else {
print("error")
}
}.resume()
}
CodePudding user response:
Add a completion handler parameter to the getLocation
function and call it when the data task completes.
When invoking getLocation
, pass there a closure than in turn invokes getWeatherInformation
.
A couple of other improvements that I'd suggest:
- Move side effects like persisting a location from the method body to its completion handler. Then its responsibility will be to just fetch a location, parse it and hand it over to completion handler. Much more reusable.
- Create a new type for different errors that may occur when making a network request. That will allow you to find issues quicker and inform the user appropriately in the UI depending on what happened. My example below might be a bit too detailed for the cause but hope it gives you an idea.
- Use Swift's
Result
for the return type of the completion handler to let the caller know whether the network fetch succeeded of failed.
Here's how I'd suggest writing it:
enum LocationFetchError: Error {
case malformedUrl
case networkError(innerError: Error)
case noData
case unexpectedResponse
case parsingError
}
func getLocation(cityName: String, completion: @escaping (Result<Location, Error>) -> Void) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&appid=\(keyValue)") else {
completion(.failure(LocationFetchError.malformedUrl))
return
}
let session = URLSession(configuration: .default)
session.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(LocationFetchError.networkError(innerError: error)))
return
}
guard let data = data else {
completion(.failure(LocationFetchError.noData))
return
}
let successRange = (200..<300)
guard let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) else {
completion(.failure(LocationFetchError.unexpectedResponse))
return
}
let decoder = JSONDecoder()
guard let location = try? decoder.decode(Location.self, from: data) else {
completion(.failure(LocationFetchError.parsingError))
return
}
completion(.success(location))
}.resume()
}
And here's how you'd call this method:
getLocation(cityName: "Springfield", completion: { [weak self] result in
switch result {
case .success(let location):
DispatchQueue.main.async {
self?.findLocation(location: location)
}
self?.getWeatherInformation(lat: location.lat, lon: location.lon)
case .failure(let error):
print(error.localizedDescription)
// TODO: show alert if needed, don't forget to dispatch to the main queue
}
})
I'd recommend applying the same techniques to getWeatherInformation
. In this example I haven't done it but I hope you get the idea.