【问题标题】:Swift: Update UI - Entire function on main thread or just the UI update?Swift:更新 UI - 主线程上的整个函数还是只是 UI 更新?
【发布时间】:2018-06-14 13:59:46
【问题描述】:

我了解到 UI 应该始终在主线程上更新。但是,当谈到实现这些更新的首选方法时,我有点困惑。

我有各种功能来执行一些条件检查,然后使用结果来确定如何更新 UI。我的问题是整个函数应该在主线程上运行吗?应该只更新 UI 吗?我可以/应该在另一个线程上运行条件检查吗?这是否取决于函数的作用或您希望它完成的速度?

一个无需线程即可更改 ImageView 内图像的函数示例:

@IBAction func undoPressed(_ sender: Any) {
        if !previousDrawings.isEmpty {
            previousDrawings.remove(at: previousDrawings.count - 1)
            if let lastDrawing = previousDrawings.last {
                topImageView.image = lastDrawing
            }
            else {
                // empty
                topImageView.image = nil
            }
        }
    }

我应该在主线程上设置topImageView.image 吗?像这样:

 @IBAction func undoPressed(_ sender: Any) {
        if !previousDrawings.isEmpty {
            previousDrawings.remove(at: previousDrawings.count - 1)
            if let lastDrawing = previousDrawings.last {
                DispatchQueue.main.async {
                    self.topImageView.image = lastDrawing
                }
            }
            else {
                DispatchQueue.main.async {
                    self.topImageView.image = nil
                }
            }
        }
    }

我应该使用后台线程进行条件检查吗?像这样:

@IBAction func undoPressed(_ sender: Any) {
        DispatchQueue.global(qos: .utility).async {
            if !previousDrawings.isEmpty {
                previousDrawings.remove(at: previousDrawings.count - 1)
                if let lastDrawing = previousDrawings.last {
                    DispatchQueue.main.async {
                        self.topImageView.image = lastDrawing
                    }
                }
                else {
                    DispatchQueue.main.async {
                        self.topImageView.image = nil
                    }
                }
            }
        }
    }

如果有人可以解释首选哪种方法以及为什么这会真正有帮助。

【问题讨论】:

  • GAK。远离 GCD。您显示的所有代码都不应该在后台线程上执行,因此您根本不需要像 DispatchQueue.global(qos: .utility).asyncDispatchQueue.main.async 这样的任何 GCD 调用。看我的回答。您的函数不会在后台线程上调用,除非在特殊的、有据可查的情况下。
  • “我应该使用后台线程进行条件检查吗?像这样:”块中的代码会引起很多混乱,因为您现在正在不同线程上执行一些代码,并且它可能(可能)以与您预期不同的顺序执行。

标签: ios swift multithreading user-interface


【解决方案1】:

备份。除特殊情况外,所有您的代码都在主线程上运行。例如,UIAction 方法总是在主线程上执行,UIViewController 定义的所有方法及其各种子类也是如此。事实上,你可以肯定地说 UIKit 方法是在主线程上执行的。同样,您的方法只会在非常特殊的情况下在后台线程上调用,这些情况都有很好的记录。

您可以使用 GCD 在后台线程上运行代码块。在这种情况下,代码正在后台线程上运行,因为您明确要求这样做。

默认情况下,一些系统函数(如URLSession)调用它们的委托方法/在后台线程上运行它们的完成处理程序。这些都是有据可查的。对于像 AlamoFire 或 FireBase 这样的第三方库,您必须阅读文档,但是在后台线程上调用的任何代码都应该有很好的文档记录,因为您必须对在后台线程上运行的代码采取特殊的预防措施。

使用后台线程的通常原因是,长时间运行的任务可以运行到完成,而不会在用户界面完成之前冻结用户界面。

例如,一个常见的模式是使用URLSession 从远程服务器读取一些JSON 数据。完成处理程序在后台线程上调用,因为解析返回的数据可能需要一些时间。但是,一旦完成解析,您将在对主线程的 GCD 调用中封装一个更新 UI 的调用,因为 UI 更改必须在主线程上执行。

【讨论】:

    【解决方案2】:

    首先,您的 undoPressed 方法将在主队列中调用。

    在第一组代码中,所有内容都将在主队列中。

    在第二组代码中,使用DispatchQueue.main.async 毫无意义,因为其余代码已经在主队列中。

    所以实际上,您仅有的两个明智的选择是 1 和 3。

    鉴于您的代码,选项 1 很好。如果在后台运行的代码花费了很短的时间来执行,您只会想使用选项 3。由于您在这里的代码很简单,几乎不需要花时间来执行,所以这里的选项 3 没有意义。

    所以只需使用你的第一组代码就可以了。

    当需要执行大循环或计算复杂算法或执行任何类型的网络访问时,不必担心将代码移至后台。

    【讨论】:

    • 既然我现在知道 IBActions 在主线程上执行,如果代码在标准 func 中,您还会提供相同的建议吗?
    • 标准函数是什么意思?如果您需要在后台队列上调用该函数,那么您需要确保 UI 已在主队列上更新。那么你的问题就更有意义了。使用DispatchQueue.main.async应该包裹多少代码?同样,在这种情况下,非 UI 代码非常简单,您可以安全地在主队列上运行所有代码。
    • 我的意思是删除 @IBAction 所以我只需要 func undoPressed() 不会在主线程上自动调用。
    • 我的回答适用于在主队列上调用的任何函数。我之前的评论涵盖了在后台队列上调用该方法的情况。
    猜你喜欢
    • 2017-05-23
    • 2013-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-30
    • 1970-01-01
    相关资源
    最近更新 更多