首页 > 解决方案 > 斯威夫特 || 从 API 调用返回可由其他方法使用的类

问题描述

我正在从移动应用程序调用 HERE Weather API,需要将当前天气作为对象返回,以便其他方法可以在给定时间段内使用该信息(例如,30 分钟更新间隔)。

我对基于此站点https://fluffy.es/return-value-from-a-closure/的异步调用有一个粗略的了解,但我仍然遇到无法访问我在外面创建的 Weather 对象的问题完成处理程序的关闭。

我从 Weather 类中提供了 Get 方法。我还在 AppDelegates 文件中提供了对该 Get 方法的调用。

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)

在我的 Weather.Get 调用结束时,我应该能够从其他方法访问 testWeather 对象。具体来说,一种根据该地区当前天气修改限速的方法,该方法由 Weather.Get 调用返回。

标签: iosswiftasynchronouscompletionhandler

解决方案


您没有理解的是,您的 GET 调用中的完成处理程序与原始 Weather API 调用一样异步。因此,您不能在它之后运行任何代码,这取决于其中的内容。事件顺序如下:

// 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

此外,testWeather最后一行是什么?是财产self.testWeather吗?它需要是。但即使是,你也忽略了给出self.testWeather一个值;这if let testWeather是一个不同 testWeather的(它只对完成处理程序是本地的,不能“泄漏”出来)。但是,即使您这样做了,它仍然无法工作,因为代码仍然会以错误的顺序运行:

// 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

尽管如此,记住写入self.testWeather(5) 至少会允许其他代码访问该值,前提是它稍后运行。


推荐阅读