[英]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.