Home > Blockchain >  Can I set the order of execution of functions in swift?
Can I set the order of execution of functions in swift?

Time:05-18

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.

  • Related