Swift: 如何将worker放到单独的线程中,在主视图中显示结果?

Swift: How to put worker into separate thread, show results in main view?

我想知道是否有一种简单的方法可以做到这一点:将一个long-运行 worker 放到一个单独的线程中,以免阻塞UI。并在主视图中显示结果。在 SwiftUI 或 UI 工具包下。我在网上找到的都非常复杂。还是 Swift 中有完全不同的方法?

我制作了一个简约的 Python 程序来展示我想做什么。它在 MacOS 中显示 WiFi 信号强度。

import time
import sys
import subprocess
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout
from PyQt5.QtCore import QObject, pyqtSignal, QThread

class Worker(QObject):
    send_output = pyqtSignal(str)
    def __init__(self):
        super().__init__()

    def worker(self):
        while True:
            _out = subprocess.check_output(
                ["/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport",
                 "-I"])\
                .decode("utf-8")
            self.send_output.emit("RSSI: " + _out.splitlines()[0][-3:] + " dBm")
            time.sleep(2)

class MainWindow(QWidget):
    do_work = pyqtSignal(object)
    def __init__(self):
        super().__init__()

        self.label = QLabel()
        layout = QHBoxLayout()
        self.setLayout(layout)
        layout.addWidget(self.label)
        self.show()

        self.app_thread = QThread()
        self.app = Worker()
        self.app.moveToThread(self.app_thread)
        self.app.send_output.connect(self.output_label)
        self.app_thread.started.connect(self.app.worker)
        self.app_thread.start()

    def output_label(self, _str):
        self.label.setText(_str)

if __name__ == '__main__':
    application = QApplication(sys.argv)
    mainwindow = MainWindow()
    sys.exit(application.exec())

我现在正在尝试进入 Swift。这很令人兴奋,但确实是一件大事。提前致谢!

这是一个非常宽泛的问题,所以我也将广泛地回答它。

是的,您可以 运行 在单独的线程中执行任务,然后 return 将数据发送到主线程。有多种方法可以做到这一点,一种常见的方法是 DispatchQueue.

让我们举一个过度简化的例子(绝对不是真实世界的代码):

struct ContentView : View {
    @State var result = ""
    
    var body: some View {
        Text("Result: \(result)")
            .onAppear {
                DispatchQueue.global(qos: .background).async {
                    //do your long-running code
                    var innerResult = 0
                    for i in 0...1000000 {
                        innerResult += 5
                        print(i)
                        //use DispatchQueue.main.async here if you want to update during the task
                    }
                    
                    //update at the end:
                    DispatchQueue.main.async {
                        result = "Done! \(innerResult)"
                    }
                }
            }
    }
}

在这个例子中,当视图出现时,后台线程中的任务是 运行,通过 DispatchQueue。在这种情况下,我只是利用了这样一个事实,即如果完成一百万次,打印到控制台是一项相当昂贵的操作。

完成后,它会分派回主线程并更新状态变量中的结果。

DispatchQueue 不特定于 UIKit 或 SwiftUI——使用 SwiftUI 将演示放在一起是最简单的。

如果您真的要开始编写代码来执行此操作,您可能想要研究一下 DispatchQueues 的工作原理,包括使用哪个队列、是否创建自己的队列、是否希望连续完成任务,等,但出于广泛问题的目的,即可以做到这一点(以及有多容易),这至少展示了基础知识。