在代码周围包装异步/主线程块

时间:2017-12-06 05:35:32

标签: ios swift asynchronous grand-central-dispatch ui-thread

我正在编写一些代码,让用户点击按钮登录,登录成功后,立即再次拨打电话从系统中提取数据(如果可用)。我有点困惑我需要/不需要将代码包装在不同的线程块中。我是否可以将所有内容放在DispatchQueue.main.async中,因为我在主线程上进行UI工作?这是我的代码流 - 我想知道是否有人可以审查并让我知道我是否在异步/主线程块中包含了正确的包装代码结构。

let messageFrame = AlertCreator.progressBarDisplayer(view: self.view, message: "Loading", true)

DispatchQueue.main.async(execute: {

  //Present loading spinner while call is made
  self.view.addSubview(messageFrame)

  //Make an AUTH call using URLSession.shared.dataTask (with callback)                   
  UserSecurityService.AuthenticateUser(username: self.txtUsername.text!, password: self.txtPassword.text!)
     { (authenticationResponse) -> () in

        if (authenticationResponse.Status == ResponseCode.LOGIN_SUCCESS)
         {    
              //If this user logged in successfully, and now need to import data, then do so, otherwise just proceed to the app.                  
              if (authenticationResponse.Value!.HasDataForImport) {

                         //Make ANOTHER async call using URLSession.shared.dataTask (with callback) 
                          UserDataService.GetUserSettings()
                                { response in
                                     //Remove the spinner
                                     messageFrame.removeFromSuperview()

                                    if (response.Status == ResponseCode.OK)
                                    {
                                        //Success, go to dashboard
                                        self.presentHomeViewController()
                                    }
                                    else {
                                        //Show alert failure, with clicking 'ok' firing a callback to take the user to the dashboard
                                    }
                               }
                   }
                  else {
                      //Data does not exist, so just stop the spinner and take the user to the dashboard
                        self.presentHomeViewController()
                  }
          else if (authenticationResponse.Status == ResponseCode.INVALID_USERNAME_PASSWORD) {
                 //User entered the wrong username/password
                  messageFrame.removeFromSuperview()
                 <alert is created/presented here>            
                 }
}) //main dispatch async execute

如果您注意到,我正在进行异步调用以进行身份​​验证,这会在UI上执行某些操作(显示一个微调器以阻止任何其他活动,检查登录是否成功)并删除微调器。它还可能呈现警报,然后将用户带到视图控制器。

我的具体问题是:

  1. 我不应该在DispatchQueue.main.async中包装整个块,但是 而只是两个网络电话?我已经做了各种各样的组合,其中大多数都有效(有些崩溃了),所以我不完全确定我是否只需要DispatchQueue.main.async,或者需要嵌套在其中的东西,比如一个DispatchQueue.global(qos:DispatchQoS.QoSClass.userInteractive).async?
  2. 我应该打开每个网络电话,还是仅打开外部电话
  3. 我是否应该在自己的块中包装与UI相关的内容,例如当我出现警报,查看控制器,甚至停止时 旋转器? 3。
  4. 感谢您提供任何帮助/建议!

1 个答案:

答案 0 :(得分:3)

我尝试在您的问题中添加DispatchQueue.mainDispatchQueue.global存根,以显示如何以及何时在队列之间切换。这可能不是一个完整的复制,粘贴解决方案,因为我没有完整的代码,所以我无法编译它。这仅用于说明如何使用DispatchQueue

let messageFrame = AlertCreator.progressBarDisplayer(view: self.view, message: "Loading", true)
        //Present loading spinner while call is made
        self.view.addSubview(messageFrame)

        DispatchQueue.global(qos: .default).async {
            //Make an AUTH call using URLSession.shared.dataTask (with callback)
            UserSecurityService.AuthenticateUser(username: self.txtUsername.text!, password: self.txtPassword.text!)
            { (authenticationResponse) -> () in

                if (authenticationResponse.Status == ResponseCode.LOGIN_SUCCESS)
                {
                    //If this user logged in successfully, and now need to import data, then do so, otherwise just proceed to the app.
                    if (authenticationResponse.Value!.HasDataForImport) {

                        //Make ANOTHER async call using URLSession.shared.dataTask (with callback)
                        UserDataService.GetUserSettings()
                            { response in
                                //Remove the spinner
                                messageFrame.removeFromSuperview()

                                if (response.Status == ResponseCode.OK)
                                {
                                    DispatchQueue.main.async {
                                        //Success, go to dashboard
                                        self.presentHomeViewController()
                                    }
                                }
                                else {
                                    DispatchQueue.main.async {
                                        //Show alert failure, with clicking 'ok' firing a callback to take the user to the dashboard
                                    }
                                }
                        }
                    }
                    else {
                        DispatchQueue.main.async {
                            //Data does not exist, so just stop the spinner and take the user to the dashboard
                            self.presentHomeViewController()
                        }
                    }
                    else if (authenticationResponse.Status == ResponseCode.INVALID_USERNAME_PASSWORD) {
                        DispatchQueue.main.async {
                            //User entered the wrong username/password
                            messageFrame.removeFromSuperview()
                            <alert is created/presented here>
                        }
                    }
                }
                // final = final?.replacingOccurrences(of: "Rs.", with: "")
            }
        }

建议(排序)

  

我不应该在DispatchQueue.main.async中包装整个块,但是   而只是两个网络电话?我做过各种组合,   其中大部分都有效(有些已经崩溃了),所以我不完全确定   如果我只需要DispatchQueue.main.async,或者需要嵌套的东西   在它内部,如DispatchQueue.global(qos:   DispatchQoS.QoSClass.userInteractive).async以及?

你永远不应该在DispatchQueue.main.async中包装Web服务调用,你应该将它包装在DispatchQueue.global(qos: .default).async我假设服务质量(QOS)默认通常情况通常如此,除非您对backgroundhightlow有特定要求。 Thumb规则,更新主线程上的所有UI组件。由于您的主要话题与序列化 MainQueue 相关联,因此切换到mainQueue上下文非常重要。

DispatchQueue.main.async只会异步获取主线程,DispatchQueue.main.sync将尝试通过暂停主线程的当前执行来同步获取主线程。但两者都可以让您访问主线程因此DispatchQueue.main.async不是制作非UI相关调用(如Web服务)的理想方式。

在主线程上创建一个长的,非ui相关的任务的问题是,在Application的整个生命周期中只能有一个主线程,因为它忙于处理与非UI相关的任务,并且因为Main Queue是{{ 1}}所有其他ui任务将在主队列中等待主线程饿死,因此将导致无响应的用户界面或最坏的情况(最好用户:P)导致iOS杀死你的应用。

  

我应该打开每个网络电话,还是仅打开外部电话

这取决于您的网络电话是如何设计的。如果使用Serialized类型的框架,即使在非ui线程上调用Web服务,它们也会在主线程上返回结果。在这种情况下,必须再次切换到非ui线程。因此,在这些情况下,您必须将每个Web服务调用包装在Alamofire/AFNewtorking其他外部应该可以正常运行:)

在上面发布的答案中假设您的Web服务调用完成块使用非ui线程返回响应,因此我已将两个Web服务调用包装在单个DispatchQueue.global.async中,如果不是这样的话,请修改它。

  

我是否应该将与UI相关的内容包装在自己的块中   例如,当我提出警报,查看控制器,甚至停止   旋转器

再取决于。如果控件到达你应该使用的主线程之外的任何线程上的UI组件更新语句DispatchQueue.global.async如果你确定你的控件总是在主线程上达到这样的语句你不需要包装它们。如果您不确定,那么您可以盲目地在这样的语句周围添加一个块,或者通过添加if条件来检查线程是否是主线程来做出更明智的决定:)

最后一条建议:上下文切换并不便宜,所以明智地使用它。

希望有所帮助