I am making a call to HERE Weather API from a mobile App and need to return the current weather as an object so that other methods can use that information for a given time period (eg. 30 min update intervals).
I have a rough understanding of asynchronous calls based on this site https://fluffy.es/return-value-from-a-closure/ but I am still running into my problem where I can't access the Weather object I create outside of the closure of the completionHandler.
I have provided the Get method from my Weather class. I have also provided a call to that Get method in the AppDelegates file.
public static func Get(lat:String, long:String, completionHandler: @escaping (Weather?) -> Void) {
//Keys go here
var request = URLRequest(
url: URL(string: "https://weather.cit.api.here.com/weather/1.0/report.json?app_code=-fJW5To2WdHdwQyYr_9ExQ&app_id=2m83HBDDcwAqTC2TqdLR&product=observation&latitude="+lat+"&longitude="+long+"&oneobservation=true")!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request, completionHandler: { dat, response, error in
do {
guard let json = try? JSONSerialization.jsonObject(with: dat as! Data, options: []) else {throw JSONParseError.missingJSON}
//parse json for dictionaries
guard let response = json as? [String: Any] else {throw JSONParseError.responseNotADictionary}
guard let observations = response["observations"] as? [String: Any] else {throw JSONParseError.observationsNotADictionary}
guard let locationJSON = observations["location"] as? [Any] else {throw JSONParseError.locationNotAnArray}
guard let location = locationJSON.first as? [String: Any] else {throw JSONParseError.locationNotADictionary}
guard let observationJSON = location["observation"] as? [Any] else {throw JSONParseError.observationNotAnArray}
guard let observation = observationJSON.first as? [String: Any] else {throw JSONParseError.observationNotADictionary}
//search dictionaries for values
guard let feedCreation = response["feedCreation"] as? String else {throw JSONParseError.missingFeedCreationObject}
guard let city = location["city"] as? String else {throw JSONParseError.missingCityObject}
guard let state = location["state"] as? String else {throw JSONParseError.missingStateObject}
guard let temperature = observation["temperature"] as? String else {throw JSONParseError.missingTemperatureObject}
guard let icon = observation["icon"] as? String else {throw JSONParseError.missingIconObject}
guard let iconName = observation["iconName"] as? String else {throw JSONParseError.missingIconNameObject}
//create weather object and return
guard let currentWeather = Weather(feedCreation: feedCreation,state: state,city: city,temperature: temperature,icon: icon,iconName: iconName) else {throw WeatherObjectCreationError.objectCreationFailed}
completionHandler(currentWeather)
} catch {
print("JSON Serialization error")
}
}).resume()
}
Weather.Get(lat:"32.9005", long:"-96.7869", completionHandler: {currentWeather in
if let testWeather = currentWeather{
//Works fine
print(testWeather.city)
print("completionHandler")
}
})
//Error says testWeather is not intialized
print(testWeather.city)
At the end of my Weather.Get call I should be able to access the testWeather object from other methods. Specifically, a method that modifies the speed limit based on the current weather in the area, which is returned by the Weather.Get call.
What you have not understood is that the completion handler in your GET call is every bit as asynchronous as the original Weather API call. Thus you cannot run any code after it that depends on what's in it. The order of events is as follows:
Moreover, what is the
testWeather
in the last line? Is it a property,self.testWeather
? It would need to be. But even if it is, you have neglected to giveself.testWeather
a value; theif let testWeather
is a differenttestWeather
(it is local to the completion handler only and cannot "leak" out of it). However, even if you had done that, it still wouldn't work, because the code would still run in the wrong order:Still, remembering to write to
self.testWeather
(5) would at least allow other code to access this value, provided it runs later.