如何在收到数据后移动到下一个视图?

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 直接传递给视图模型,但仍然没有成功。

这可以像下面这样一起使用 DispatchGroupBlockOperation 来实现:

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...
    }
}

结论

我希望答案能让您了解为什么您的代码不起作用,以及如何转换任何现有的此类代码以异步方式正确执行。