Swift:如何在不等待进程完成的情况下读取子进程中的标准输出 [英] Swift: How to read standard output in a child process without waiting for process to finish

查看:117
本文介绍了Swift:如何在不等待进程完成的情况下读取子进程中的标准输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个外部控制台应用程序(在OS X上),大约每秒发出一次从1到100的整数序列到标准输出。

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

代码有效-即,它从命令行实用程序读取输出并修改进度指示器,但它会使所有更改在之后退出(并使我的UI同时等待)。

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

我将如何进行设置,以便它读取后台应用程序的输出并实时更新进度指示器ime?

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

供以后参考,以下是我如何使其最终工作(现在已针对Swift 3更新):

For future reference, here is how I got it to work in the end (now updated for Swift 3):

class ViewController: NSViewController {

    @IBOutlet weak var progressIndicator: NSProgressIndicator!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.


        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
        let outHandle = pipe.fileHandleForReading
        outHandle.waitForDataInBackgroundAndNotify()

        var progressObserver : NSObjectProtocol!
        progressObserver = NotificationCenter.default.addObserver(
            forName: NSNotification.Name.NSFileHandleDataAvailable,
            object: outHandle, queue: nil)
        {
            notification -> Void in
            let data = outHandle.availableData

            if data.count > 0 {
                if let str = String(data: data, encoding: String.Encoding.utf8) {
                    if let newValue = Double(str.trimEverything) {
                        self.progressIndicator.doubleValue = newValue
                    }
                }
                outHandle.waitForDataInBackgroundAndNotify()
            } else {
                // That means we've reached the end of the input.
                NotificationCenter.default.removeObserver(progressObserver)
            }
        }

        var terminationObserver : NSObjectProtocol!
        terminationObserver = NotificationCenter.default.addObserver(
            forName: Process.didTerminateNotification,
            object: task, queue: nil)
        {
            notification -> Void in
            // Process was terminated. Hence, progress should be 100%
            self.progressIndicator.doubleValue = 100
            NotificationCenter.default.removeObserver(terminationObserver)
        }

        task.launch()

    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

}


// This is just a convenience extension so that I can trim
// off the extra newlines and white spaces before converting
// the input to a Double.
fileprivate extension String {
    var trimEverything: String {
        return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
    }
}

现在,一旦子进程完成,进度条将前进到60%,然后跳到100%。

Now, the progress bar progresses to 60% and then jumps up to 100%, once the child process has completed.

推荐答案

您正在同步读取主线程,因此直到函数返回主循环后才更新UI。

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:

  • Do the reading from the pipe on a background thread (e.g. by dispatching it to a background queue – but don't forget to dispatch the UI updates to the main thread again).
  • Use notifications to read asynchronously from the pipe (see Real time NSTask output to NSTextView with Swift for an example).

这篇关于Swift:如何在不等待进程完成的情况下读取子进程中的标准输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆