如何在收到数据后移动到下一个视图?
How to move to the next view upon data reception?
我正在努力触发负责在正确时间更改视图的逻辑。让我解释一下。
我有一个视图模型,其中包含一个名为 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
}
}
这就是 Web 服务结构中发生的事情:
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
}
}
正如您所见,转义闭包(其代码在视图模型中)returns serverResponse.response(可以是“成功”或“失败”),然后存储在名为 serverResponse 的变量中。然后,requestNewUser() returns 该值。最后,createNewUserVM() 函数 returns 返回的字符串,此时整个逻辑结束。
为了移动到下一个视图,我的想法是像这样简单地检查返回值:
serverResponse = self.signupViewModel.createNewUserVM()
if serverResponse == "success" {
//move to the next view
}
然而,在写了一些打印语句之后,我发现 if 语句被触发 太早了,大约在转义闭包 returns值,它发生在视图模型 returns 之前。我试图通过使用一些 DispatchQueue 逻辑来解决问题,但没有任何效果。我还尝试像这样实现一个 while 循环:
while serverResponse.isEmpty {
//fetch the data
}
//at this point, serverResponse is not empty
//move to the next view
这是为了说明代码的异步性质。
我还尝试将处理视图显示背后逻辑的 EnvironmentObject 直接传递给视图模型,但仍然没有成功。
这可以像下面这样一起使用 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.
注意:阅读评论以了解。我在 swift 操场上有 运行 这个并且工作正常。在操场上复制过去的代码,玩得开心!!!
正如 matt 所指出的,您似乎在代码中混淆了同步和异步流程。但我认为主要问题源于您认为 URLSession.shared.dataTask
同步执行的事实。它实际上是异步执行的。因此,iOS 不会等到收到服务器响应后才执行其余代码。
要解决此问题,您需要仔细阅读并将有问题的部分转换为异步代码。由于在您的情况下答案并非微不足道,我将尽力帮助您将代码转换为正确的异步代码。
1。让我们从 Webservices
结构
开始
当您调用 dataTask
方法时,iOS 会创建一个 URLSessionDataTask
并 return 发送给您。你在它上面调用 resume()
,它开始在不同的线程上执行 异步 .
因为它是异步执行的,所以 iOS 不会等待它 return 继续执行其余代码。一旦 resume()
方法 returns,requestNewUser
方法也会 returns。当您的应用收到 JSON 响应时,requestNewUser
很久以前就已经 return 编辑了。
因此,要正确传回您的响应,您需要做的是以异步方式通过“完成”函数类型传递它。我们也不需要那个函数来 return 任何东西——它可以处理响应并进行其余的工作。
所以这个方法签名:
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
方法现在没有 return 任何东西。因此,我们需要在其余代码中适应该更改。让我们将 createNewUserVM
方法从同步方法转换为异步方法。我们还应该询问函数的调用代码,该函数将从我们的 Webservice
class.
接收结果
所以你的 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...
}
}
结论
我希望答案能让您了解为什么您的代码不起作用,以及如何转换任何现有的此类代码以异步方式正确执行。
我正在努力触发负责在正确时间更改视图的逻辑。让我解释一下。
我有一个视图模型,其中包含一个名为 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
}
}
这就是 Web 服务结构中发生的事情:
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
}
}
正如您所见,转义闭包(其代码在视图模型中)returns serverResponse.response(可以是“成功”或“失败”),然后存储在名为 serverResponse 的变量中。然后,requestNewUser() returns 该值。最后,createNewUserVM() 函数 returns 返回的字符串,此时整个逻辑结束。
为了移动到下一个视图,我的想法是像这样简单地检查返回值:
serverResponse = self.signupViewModel.createNewUserVM()
if serverResponse == "success" {
//move to the next view
}
然而,在写了一些打印语句之后,我发现 if 语句被触发 太早了,大约在转义闭包 returns值,它发生在视图模型 returns 之前。我试图通过使用一些 DispatchQueue 逻辑来解决问题,但没有任何效果。我还尝试像这样实现一个 while 循环:
while serverResponse.isEmpty {
//fetch the data
}
//at this point, serverResponse is not empty
//move to the next view
这是为了说明代码的异步性质。 我还尝试将处理视图显示背后逻辑的 EnvironmentObject 直接传递给视图模型,但仍然没有成功。
这可以像下面这样一起使用 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.
注意:阅读评论以了解。我在 swift 操场上有 运行 这个并且工作正常。在操场上复制过去的代码,玩得开心!!!
正如 matt 所指出的,您似乎在代码中混淆了同步和异步流程。但我认为主要问题源于您认为 URLSession.shared.dataTask
同步执行的事实。它实际上是异步执行的。因此,iOS 不会等到收到服务器响应后才执行其余代码。
要解决此问题,您需要仔细阅读并将有问题的部分转换为异步代码。由于在您的情况下答案并非微不足道,我将尽力帮助您将代码转换为正确的异步代码。
1。让我们从 Webservices
结构
开始
当您调用 dataTask
方法时,iOS 会创建一个 URLSessionDataTask
并 return 发送给您。你在它上面调用 resume()
,它开始在不同的线程上执行 异步 .
因为它是异步执行的,所以 iOS 不会等待它 return 继续执行其余代码。一旦 resume()
方法 returns,requestNewUser
方法也会 returns。当您的应用收到 JSON 响应时,requestNewUser
很久以前就已经 return 编辑了。
因此,要正确传回您的响应,您需要做的是以异步方式通过“完成”函数类型传递它。我们也不需要那个函数来 return 任何东西——它可以处理响应并进行其余的工作。
所以这个方法签名:
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
方法现在没有 return 任何东西。因此,我们需要在其余代码中适应该更改。让我们将 createNewUserVM
方法从同步方法转换为异步方法。我们还应该询问函数的调用代码,该函数将从我们的 Webservice
class.
所以你的 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...
}
}
结论
我希望答案能让您了解为什么您的代码不起作用,以及如何转换任何现有的此类代码以异步方式正确执行。