[英]Modify Global Variable Inside Closure (Swift 4)
我正在尝试使用此函数修改全局变量currentWeather(类型CurrentWeather),该函数旨在使用从URL检索的信息更新所述变量,并返回表示其成功的布尔值。 但是,该函数返回false,因为currentWeather仍然为零。 我认识到dataTask是异步的,并且该任务在与应用程序并行的背景下运行,但是我不明白这对我要完成的工作意味着什么。 我也无法在do块之后更新currentWeather,因为退出该块后无法再识别天气。 我曾尝试使用“ self.currentWeather”,但被告知这是一个无法解析的标识符(可能是因为该函数也是全局的,并且没有“ self”?)。
该URL当前无效,因为我拿出了我的API密钥,但否则它可以正常工作,并且CurrentWeather结构是可分解的。 打印currentWeatherUnwrapped也始终成功。
我确实查看了Stack Overflow,并查看了Apple的官方文档,但找不到能回答我问题的内容,但也许我不够彻底。 很抱歉,如果这是一个重复的问题。 任何进一步相关阅读的方向也表示赞赏! 对于缺乏与最佳编码实践的一致性,我深表歉意。 非常感谢大家!
func getCurrentWeather () -> Bool {
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/\(state)/\(city).json"
guard let url = URL(string: jsonUrlString) else { return false }
URLSession.shared.dataTask(with: url) { (data, response, err) in
// check error/response
guard let data = data else { return }
do {
let weather = try JSONDecoder().decode(CurrentWeather.self, from: data)
currentWeather = weather
if let currentWeatherUnwrapped = currentWeather {
print(currentWeatherUnwrapped)
}
} catch let jsonErr {
print("Error serializing JSON: ", jsonErr)
}
// cannot update currentWeather here, as weather is local to do block
}.resume()
return currentWeather != nil
}
当您执行这样的异步调用时,您的函数将返回很长时间,而dataTask将没有任何值要返回。 您需要做的是在函数中使用完成处理程序。 您可以像这样将其作为参数传递:
func getCurrentWeather(completion: @escaping(CurrentWeather?, Error?) -> Void) {
//Data task and such here
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/\(state)/\(city).json"
guard let url = URL(string: jsonUrlString) else { return false }
URLSession.shared.dataTask(with: url) { (data, response, err) in
// check error/response
guard let data = data else {
completion(nil, err)
return
}
//You don't need a do try catch if you use try?
let weather = try? JSONDecoder().decode(CurrentWeather.self, from: data)
completion(weather, err)
}.resume()
}
然后调用该函数如下所示:
getCurrentWeather(completion: { (weather, error) in
guard error == nil, let weather = weather else {
if weather == nil { print("No Weather") }
if error != nil { print(error!.localizedDescription) }
return
}
//Do something with your weather result
print(weather)
})
您从根本上误解了异步功能是如何工作的。 您的函数将在URLSession's
dataTask
甚至开始执行之前返回。 网络请求可能需要几秒钟才能完成。 您要求它为您获取一些数据,为它提供一个代码块,以在数据下载后立即执行,然后继续您的业务。
您可以确定dataTask的resume()
调用之后的行将在加载新数据之前运行。
当数据任务的完成块中有可用数据时,您需要将要运行的代码放入。 (成功读取数据后,您的语句print(currentWeatherUnwrapped)
将运行。)
您只需要关闭即可。
您不能拥有同步的return语句来返回本质上是异步的Web服务调用的响应。 您需要为此关闭。
您可以如下修改答案。 因为您未在评论中回答我的问题,所以我自由地返回了wether对象,而不是返回没有意义的bool。
func getCurrentWeather (completion : @escaping((CurrentWeather?) -> ()) ){
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/"
guard let url = URL(string: jsonUrlString) else { return false }
URLSession.shared.dataTask(with: url) { (data, response, err) in
// check error/response
guard let data = data else { return }
do {
let weather = try JSONDecoder().decode(CurrentWeather.self, from: data)
CurrentWeather.currentWeather = weather
if let currentWeatherUnwrapped = currentWeather {
completion(CurrentWeather.currentWeather)
}
} catch let jsonErr {
print("Error serializing JSON: ", jsonErr)
completion(nil)
}
// cannot update currentWeather here, as weather is local to do block
}.resume()
}
假设currentWeather
是CurrentWeather
类中的静态变量,则可以更新全局变量并将实际数据返回给调用方,如上所示
编辑:
正如Duncan在下面的评论中指出的那样,以上代码在后台线程中执行完成块。 所有UI操作必须仅在主线程上完成。 因此,在更新UI之前切换线程非常重要。
两种方式:
1-确保在主线程上执行完成块。
DispatchQueue.main.async {
completion(CurrentWeather.currentWeather)
}
这样可以确保将来使用getCurrentWeather
都不必担心切换线程,因为您的方法会处理它。 如果您的完成块仅包含更新UI的代码,则很有用。 用这种方法完成块中更长的逻辑将负担主线程。
2-在更新UI元素时,在完成块中作为参数传递给getCurrentWeather
时,请确保将这些语句包装在
DispatchQueue.main.async {
//your code to update UI
}
编辑2:
正如Leo Dabus在下面的评论中指出的那样,我应该运行完成块而不是guard let url = URL(string: jsonUrlString) else { return false }
这是一个复制粘贴错误。 我复制了OP的问题,并急忙意识到存在一个return语句。
尽管在这种情况下将错误作为参数是可选的,并且完全取决于您设计错误处理模型的方式,但我感谢Leo Dabus提出的想法,该想法是更通用的方法,因此可以更新我的答案以将错误作为参数。
现在,在某些情况下,我们可能还需要发送自定义错误,例如,如果guard let data = data else { return }
返回false而不是简单地调用return,那么您可能需要返回一个自己的错误,即输入无效或类似的错误像那样。
因此,我可以自由声明自己的自定义错误,您也可以使用该模型来处理您的错误处理
enum CustomError : Error {
case invalidServerResponse
case invalidURL
}
func getCurrentWeather (completion : @escaping((CurrentWeather?,Error?) -> ()) ){
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/"
guard let url = URL(string: jsonUrlString) else {
DispatchQueue.main.async {
completion(nil,CustomError.invalidURL)
}
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
// check error/response
if err != nil {
DispatchQueue.main.async {
completion(nil,err)
}
return
}
guard let data = data else {
DispatchQueue.main.async {
completion(nil,CustomError.invalidServerResponse)
}
return
}
do {
let weather = try JSONDecoder().decode(CurrentWeather.self, from: data)
CurrentWeather.currentWeather = weather
if let currentWeatherUnwrapped = currentWeather {
DispatchQueue.main.async {
completion(CurrentWeather.currentWeather,nil)
}
}
} catch let jsonErr {
print("Error serializing JSON: ", jsonErr)
DispatchQueue.main.async {
completion(nil,jsonErr)
}
}
// cannot update currentWeather here, as weather is local to do block
}.resume()
}
正如您所指出的, data ask
是async
,这意味着您不知道何时完成。
一种选择是通过不提供返回值,而是回调/关闭来将包装函数getCurrentWeather
修改为异步。 然后,您将不得不在其他地方处理异步特性。
场景中您可能想要的另一个选项是使data task
synchronous
如下所示:
func getCurrentWeather () -> Bool {
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/\(state)/\(city).json"
guard let url = URL(string: jsonUrlString) else { return false }
let dispatchGroup = DispatchGroup() // <===
dispatchGroup.enter() // <===
URLSession.shared.dataTask(with: url) { (data, response, err) in
// check error/response
guard let data = data else {
dispatchGroup.leave() // <===
return
}
do {
let weather = try JSONDecoder().decode(CurrentWeather.self, from: data)
currentWeather = weather
if let currentWeatherUnwrapped = currentWeather {
print(currentWeatherUnwrapped)
}
dispatchGroup.leave() // <===
} catch let jsonErr {
print("Error serializing JSON: ", jsonErr)
dispatchGroup.leave() // <===
}
// cannot update currentWeather here, as weather is local to do block
}.resume()
dispatchGroup.wait() // <===
return currentWeather != nil
}
wait
函数可以使用可以定义超时的参数。 https://developer.apple.com/documentation/dispatch/dispatchgroup否则,您的应用可能会永远等待。 然后,您将能够定义一些动作来呈现给用户。
顺便说一句,我制作了一个全功能的天气应用程序只是为了学习,因此请在GitHub https://github.com/erikmartens/NearbyWeather上查看。 希望那里的代码可以为您的项目提供帮助。 它也可以在应用商店中找到。
编辑:请理解,这个答案是为了显示如何使异步调用同步。 我并不是说这是处理网络呼叫的好习惯。 当您绝对必须从函数中获得返回值时,即使该函数内部使用了异步调用,这也是一种骇人听闻的解决方案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.