斯威夫特 ||从 API 调用返回可由其他方法使用的类

Posted

技术标签:

【中文标题】斯威夫特 ||从 API 调用返回可由其他方法使用的类【英文标题】:Swift || Returning a class that can be used by other methods from an API call 【发布时间】:2019-04-16 15:59:40 【问题描述】:

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

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

我从我的 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 调用返回。

【问题讨论】:

您已经说过没有发生的事情,但了解正在发生的事情会很有帮助。是打印"JSON Serialization error" 还是"completionHandler"?如果您在 dataTask 完成处理程序中放置一个断点,它会被命中吗? 抱歉,当我在 completionHandler 中打印 testWeather.city 时,一切都会打印出来。当我尝试在 completionHandler 之外打印时,它说 testWeather 未初始化。我的理解是,这是由于代码的执行顺序造成的(即 print(testWeather.city) 在完成处理程序执行之前执行) 没错,您只能在闭包中使用testWeather,但您可以将其传递给另一个方法或将其保存到变量中,以便代码的其他部分可以访问它。 testWeather 是您正在使用的闭包的本地。在该闭包之外无法访问它。您必须将其保存为任何其他可以稍后访问的天气对象,例如在属性中。 【参考方案1】:

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

// 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) 至少会允许 other 代码访问此值,前提是它稍后运行。

【讨论】:

这件事情在我的博文programmingios.net/…987654321@中讨论得很透彻 谢谢,我现在更好地理解了代码的执行。这是否意味着我应该移动任何其他依赖于 Weather.Get comletionHandler 内天气数据的调用? 实际上,如果您希望他们被称为以响应您现在拥有天气信息这一事实,那么绝对可以。再次,请参阅programmingios.net/what-asynchronous-means。

以上是关于斯威夫特 ||从 API 调用返回可由其他方法使用的类的主要内容,如果未能解决你的问题,请参考以下文章

从 asp.net API 中的方法返回后,如何保持线程运行?

API开发实践 查询物流路径

从 Web Api 调用 Graph Api 不会返回所有用户数据

通用方法的同步调用 Web API 服务

调用api资源并返回当前用户

ASP.NET MVC API 操作返回 404,所有其他操作都有效