我正在努力觸發負責在正確時間更改視圖的邏輯。讓我解釋。
我有一個視圖模型,其中包含一個名為 createNewUserVM() 的函式。此函式觸發另一個名為 requestNewUser() 的函式,該函式位于名為 Webservices 的結構中。
func createNewUserVM() -> String {
Webservices().requestNewUser(with: User(firstName: firstName, lastName: lastName, email: email, password: password)) { serverResponse in
guard let serverResponse = serverResponse else {
return "failure"
}
return serverResponse.response
}
}
現在這就是 Webservices 結構中發生的事情:
struct Webservices {
func requestNewUser(with user: User, completion: @escaping (Response?) -> String) -> String {
//code that creates the desired request based on the server's URL
//...
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
serverResponse = completion(nil)
}
return
}
let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
serverResponse = completion(decodedResponse)
}
}.resume()
return serverResponse //last line that gets executed before the if statement
}
}
如您所見,轉義閉包(其代碼在視圖模型中)回傳 serverResponse.response(可以是“成功”或“失敗”),然后將其存盤在名為 serverResponse 的變數中。然后,requestNewUser() 回傳該值。最后,createNewUserVM() 函式回傳回傳的字串,此時整個邏輯結束。
為了移動到下一個視圖,想法是簡單地檢查回傳值,如下所示:
serverResponse = self.signupViewModel.createNewUserVM()
if serverResponse == "success" {
//move to the next view
}
然而,在撰寫了一些列印陳述句之后,我發現 if 陳述句被觸發得太早了,大約在轉義閉包回傳值的時間,這發生在視圖模型回傳它之前。我試圖通過使用一些 DispatchQueue 邏輯來解決問題,但沒有任何效果。我也嘗試像這樣實作一個while回圈:
while serverResponse.isEmpty {
//fetch the data
}
//at this point, serverResponse is not empty
//move to the next view
這是為了說明代碼的異步性質。我還嘗試將處理視圖背后的邏輯的 EnvironmentObject 直接傳遞給視圖模型,但仍然沒有成功。
uj5u.com熱心網友回復:
正如馬特指出的那樣,您似乎在代碼中混淆了同步和異步流。但我認為主要問題源于您認為URLSession.shared.dataTask同步執行的事實。它實際上是異步執行的。因此,iOS 不會等到收到您的服務器回應后才執行其余代碼。
要解決此問題,您需要仔細閱讀有問題的部分并將其轉換為異步代碼。由于在您的情況下答案并非微不足道,因此我會盡力幫助您將代碼轉換為適當的異步。
1.讓我們從Webservices結構開始
當您呼叫該dataTask方法時,iOS 會創建 aURLSessionDataTask并將其回傳給您。你呼叫resume()它,它開始在不同的執行緒上異步執行。
因為它是異步執行的,iOS 不會等待它回傳來繼續執行其余的代碼。一旦resume()方法回傳,該requestNewUser方法也回傳。當您的應用收到 JSON 回應時,requestNewUser它早已回傳。
所以你需要做的是正確地傳遞你的回應,以異步方式通過“完成”函式型別傳遞它。我們也不需要那個函式來回傳任何東西——它可以處理回應并進行其余的作業。
所以這個方法簽名:
func requestNewUser(with user: User, completion: @escaping (Response?) -> String) -> String {
變成這樣:
func requestNewUser(with user: User, completion: @escaping (Response?) -> Void) {
外觀的變化requestNewUser如下:
func requestNewUser(with user: User, completion: @escaping (Response?) -> Void) {
//code that creates the desired request based on the server's URL
//...
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
completion(decodedResponse)
}
}.resume()
}
2.查看模型更改
該requestNewUser方法現在不回傳任何內容。因此,我們需要在其余代碼中適應這種變化。讓我們將我們的createNewUserVM方法從同步轉換為異步。我們還應該向呼叫代碼詢問一個將從我們的Webservice類接收結果的函式。
所以你的createNewUserVM變化:
func createNewUserVM() -> String {
Webservices().requestNewUser(with: User(firstName: firstName, lastName: lastName, email: email, password: password)) { serverResponse in
guard let serverResponse = serverResponse else {
return "failure"
}
return serverResponse.response
}
}
對此:
func createNewUserVM(_ callback: @escaping (_ response: String?) -> Void) {
Webservices().requestNewUser(with: User(firstName: firstName, lastName: lastName, email: email, password: password)) { serverResponse in
guard let serverResponse = serverResponse else {
callback("failure")
return
}
callback(serverResponse.response)
}
}
3. 移動到下一個視圖
現在這createNewUserVM也是異步的,我們還需要更改從控制器呼叫它的方式。
因此該代碼從此更改:
serverResponse = self.signupViewModel.createNewUserVM()
if serverResponse == "success" {
//move to the next view
}
對此:
self.signupViewModel.createNewUserVM{ [weak self] (serverResponse) in
guard let `self` = self else { return }
if serverResponse == "success" {
// move to the next view
// self.present something...
}
}
結論
我希望答案能讓您了解為什么您的代碼不起作用,以及如何將任何現有的此類代碼轉換為以異步方式正確執行。
uj5u.com熱心網友回復:
這可以通過使用DispatchGroup和BlockOperation一起實作,如下所示:
func functionWillEscapeAfter(time: DispatchTime, completion: @escaping (Bool) -> Void) {
DispatchQueue.main.asyncAfter(deadline: time) {
completion(false) // change the value to reflect changes.
}
}
func createNewUserAfterGettingResponse() {
let group = DispatchGroup()
let firstOperation = BlockOperation()
firstOperation.addExecutionBlock {
group.enter()
print("Wait until async block returns")
functionWillEscapeAfter(time: .now() 5) { isSuccess in
print("Returned value after specified seconds...")
if isSuccess {
group.leave()
// and firstoperation will be complete
} else {
firstOperation.cancel() // means first operation is cancelled and we can check later if cancelled don't execute next operation
group.leave()
}
}
group.wait() //Waits until async closure returns something
} // first operation ends
let secondOperation = BlockOperation()
secondOperation.addExecutionBlock {
// Now before executing check if previous operation was cancelled we don't need to execute this operation.
if !firstOperation.isCancelled { // First operation was successful.
// move to next view
moveToNextView()
} else { // First operation was successful.
// do something else.
print("Don't move to next block")
}
}
// now second operation depends upon the first operation so add dependency
secondOperation.addDependency(firstOperation)
//run operation in queue
let operationQueue = OperationQueue()
operationQueue.addOperations([firstOperation, secondOperation], waitUntilFinished: false)
}
func moveToNextView() {
// move view
print("Move to next block")
}
createNewUserAfterGettingResponse() // Call this in playground to execute all above code.
注意:閱讀評論以了解理解。我已經在快速的操場上運行它并且作業正常。在操場上復制過去的代碼,玩得開心!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/415005.html
標籤:
