swiftcocoa

Swift: How to read standard output in a child process without waiting for process to finish


I have an external console application (on OS X) that emits a sequence of integers from 1 to 100 to standard output, roughly once every second.

I Swift, I need to use that stream of numbers in order to update a progress indicator.

Here is the code that I have so far:

class MasterViewController: NSViewController {

@IBOutlet weak var progressIndicator: NSProgressIndicator!

override func viewDidLoad() {
    super.viewDidLoad()
    
    let task = Process()
    task.launchPath = "/bin/sh"
    task.arguments = ["-c", "sleep 1; echo 10 ; sleep 1 ; echo 20 ; sleep 1 ; echo 30 ; sleep 1 ; echo 40; sleep 1; echo 50; sleep 1; echo 60; sleep 1"]  
  
    let pipe = Pipe()
    task.standardOutput = pipe

    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    if let string = String(data: data, encoding: String.Encoding.utf8) {
        print(string)
    }
}

The code works—that is, it reads the output from the command-line utility and modifies the progress indicator accordingly—but it makes all the changes after the utility quits (and makes my UI wait in the meantime).

How would I set it up so that it reads the output from the background application and updates the progress indicator in real-time?


Solution

  • You are reading synchronously on the main thread, therefore the UI is not updated until the function returns to the main loop.

    There are (at least) two possible approaches to solve the problem: