我正在尝试使用此函数修改全局变量currentWeather(类型为CurrentWeather),这意味着使用从URL检索的信息更新所述变量并返回表示其成功的bool。但是,函数返回false,因为currentWeather仍为零。我认识到dataTask是异步的,并且任务是在与应用程序并行的后台运行,但我不明白这对我想要实现的目的意味着什么。在do块之后我也无法更新currentWeather,因为退出块后天气不再被识别。我确实尝试使用" self.currentWeather",但被告知它是一个未解析的标识符(也许是因为该函数也是全局的,并且没有" self"?)。
网址目前无效,因为我拿出了我的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
}
答案 0 :(得分:1)
当您执行这样的异步调用时,您的函数将在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)
})
答案 1 :(得分:0)
您从根本上误解了异步功能的工作原理。在URLSession's
dataTask
开始执行之前,您将返回函数。网络请求可能需要几秒钟才能完成。您要求它为您提取一些数据,给它一段代码来执行ONCE THE DATA HAS LOADLOADED,然后继续您的业务。
您可以确定dataTask resume()
调用之后的行将在新数据加载之前运行。
当数据在数据任务的完成块中可用时,您需要放置要运行的代码。 (成功读取数据后,您的语句print(currentWeatherUnwrapped)
将会运行。)
答案 2 :(得分:0)
你需要的只是一个闭包。
您无法使用同步return语句来返回Web服务调用的响应,这本身就是异步的。你需要关闭它。
您可以按照以下方式修改答案。因为你在评论中没有回答我的问题,所以我已经自由地回归了那些没有多大意义的bool而不是返回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的问题,并且匆匆地意识到有一个回复声明。
虽然在这种情况下作为参数的错误是可选的,并且完全取决于你如何设计你的错误处理模型,但我很欣赏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()
}
答案 3 :(得分:-1)
正如您所指出的,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上查看。希望那里的代码可以帮助您完成项目。它也可以在应用程序商店中找到。
编辑:请理解,此答案旨在说明如何使异步调用同步。我不是说这是处理网络呼叫的好习惯。这是一个 hacky 解决方案,用于绝对必须从函数返回值,即使它在内部使用异步调用。