Swift || Returning a class that can be used by oth

2020-05-08 07:08发布

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.

1条回答
Anthone
2楼-- · 2020-05-08 07:14

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:

// 1
Weather.Get(lat:"32.9005", long:"-96.7869", completionHandler: {currentWeather in
    if let testWeather = currentWeather{ // 3!!!
        print(testWeather.city) // 4!!!!!!
        print("completionHandler")
    }
})
print(testWeather.city) // 2

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 give self.testWeather a value; the if let testWeather is a different testWeather (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:

// 1
Weather.Get(lat:"32.9005", long:"-96.7869", completionHandler: {currentWeather in
    if let testWeather = currentWeather{ // 3
        print(testWeather.city) // 4
        print("completionHandler") 
        self.testWeather = testWeather // 5; better but it won't help the _next_ print
    }
})
print(testWeather.city) // 2

Still, remembering to write to self.testWeather (5) would at least allow other code to access this value, provided it runs later.

查看更多
登录 后发表回答