Swift:更新UI - 整个函数在主线程中还是仅仅是UI更新?

5

我读过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。你展示的代码都不应该在后台线程上执行,因此你根本不需要任何GCD调用,如DispatchQueue.global(qos: .utility).asyncDispatchQueue.main.async。请参考我的答案。除非在特殊的、有文档记录的情况下,否则你的函数不会在后台线程上被调用。 - Duncan C
你的“我是否应该为条件检查使用后台线程?像这样:”代码块中的代码会给你带来很多困惑,因为现在你的一些代码正在不同的线程上执行,而且它可能(很可能)按照你所期望的顺序执行。 - Duncan C
3个回答

8
备份。除非特殊情况,所有代码都在主线程上运行。例如,UIAction方法始终在主线程上执行,UIViewController及其各个子类定义的所有方法也是如此。实际上,可以安全地说UIKit方法在主线程上执行。再次强调,在非常特殊的情况下才会在后台线程上调用您的方法,这些情况都有很好的文档记录。

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

某些系统功能(例如URLSession)默认在后台线程上调用其委托方法/运行完成处理程序。这些都有详细的文档记录。对于像AlamoFire或FireBase这样的第三方库,您需要阅读文档,但在后台线程上调用的任何代码都应该有很好的文档记录,因为您必须为在后台线程上运行的代码采取特殊预防措施。

通常使用后台线程的原因是让长时间运行的任务完成而不会冻结用户界面,直到完成为止。

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


4
首先,您的undoPressed方法将在主队列上调用。
在第一组代码中,所有内容都在主队列上。
在第二组代码中,使用DispatchQueue.main.async是无意义的,因为其余代码已经在主队列上了。
因此,您只有两个明智的选择:1和3。
考虑到您的代码,选项1很好。只有当后台运行的代码需要执行大量时间时,才需要使用选项3。由于您在此处的代码是微不足道的,并且几乎不需要执行时间,因此在此处使用选项3没有意义。
因此,只需使用您的第一组代码即可。
当需要执行大型循环或计算复杂算法或执行任何类型的网络访问时,请担心将代码移动到后台。

既然我现在知道IBActions在主线程上执行,如果代码在标准函数中,你是否仍会提供相同的建议? - DoesData
什么是标准函数?如果您需要在后台队列上调用该函数,则需要确保在主队列上更新UI。那么你的问题就更有意义了。代码中应该包含多少DispatchQueue.main.async的使用?同样,在这种情况下,非UI代码非常简单,您可以安全地在主队列上运行所有代码。 - rmaddy
我是说移除@IBAction,这样我只会有一个func undoPressed(),它不会自动在主线程上调用。 - DoesData
1
我的回答适用于在主队列上调用的任何函数。我之前的评论涵盖了在后台队列上调用该方法的情况。 - rmaddy

-1

为了使它更简单,进行计算并更新需要在UI中反映的与该更新计算相关的所有内容应该从以下开始:

DispatchQueue.main.async{ //code }

这是使用主线程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接