0

I wonder if there is an easy way to do this: Put a long-running worker into a separate thread so as not to block the UI. And display the results in the main view. Under SwiftUI or UIKit. What I found on the web was all very complex. Or is there a completely different approach in Swift?

I made a minimalistic Python program to show what I want to do. It displays the WiFi signal strength in MacOS.

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())

I'm trying to find my way into Swift right now. It's exciting, but really a big thing. Thanks in advance!

mildspleen
  • 25
  • 5

1 Answers1

2

This is a very broad question, so I'm going to answer it rather broadly as well.

Yes, you can run tasks in separate threads and then return data to the main thread. There are number of ways to do this, a common one being DispatchQueue.

Let's take an over-simplified example (definitely not meant to be real-world code):

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)"
                    }
                }
            }
    }
}

In this example, when the view appears, a task is run in a background thread, via DispatchQueue. In this case, I'm just taking advantage of the fact that printing to the console is a rather expensive operation if done a million times.

When it finishes, it dispatches back to the main thread and updates the results in the state variable.

DispatchQueue is not specific to UIKit or SwiftUI -- it was just easiest to put the demo together using SwiftUI.

If you were to truly start writing code to do this, you'd want to do some research about how the DispatchQueues work, including which queue to use, whether to create your own, whether you want tasks done serially, etc, but for the purposes of the broad question of can this be done (and how easily), this at least shows the basics.

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Thank you so much for this very fast reply. Yes, I will definitely dive into DispatchQueue docu. Yes, print works nicely. That's not the problem. But for my case: I want to update the UI view after every iteration in the worker. Not after the worker is finished. – mildspleen Feb 27 '21 at 04:50
  • No problem -- call DispatchQueue.main.async { } inside your loop/task. – jnpdx Feb 27 '21 at 04:59
  • Yes, have tried this on my own in the meantime. Works greatly. This is what I want!!! I am happy. Now it makes sense to my to dive in and find out about the how's and why's etc. I really like the concepts of Swift. Especially on the subject of data type security. But that's really huge and difficult to figure out where to start from. But you showed me the point. Thank you so much!! – mildspleen Feb 27 '21 at 05:13