【问题标题】:Swift: How to read standard output in a child process without waiting for process to finishSwift:如何在子进程中读取标准输出而不等待进程完成
【发布时间】:2015-11-20 06:59:28
【问题描述】:

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

我是 Swift,我需要使用该数字流来更新进度指示器。

这是我目前的代码:

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 等待同时)。

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

更新

为了将来参考,这是我最终如何让它工作的(现在为 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%。

【问题讨论】:

    标签: swift cocoa


    【解决方案1】:

    您正在主线程上同步读取,因此在函数返回主循环之前不会更新 UI。

    有(至少)两种可能的方法来解决这个问题:

    • 在后台线程上从管道读取数据(例如,通过将其分派到后台队列 - 但不要忘记 再次将 UI 更新分派到主线程)。
    • 使用通知从管道异步读取(请参阅 以Real time NSTask output to NSTextView with Swift 为例)。

    【讨论】:

    • 确保让子进程将其标准输出设置为行缓冲或在每行之后显式刷新。否则,它可能会将其输出保存在内部缓冲区中,而不是实际将其写入管道。
    • 是的,第二个选项做到了。
    猜你喜欢
    • 2011-03-08
    • 1970-01-01
    • 2013-09-24
    • 2023-04-10
    • 1970-01-01
    • 2015-01-20
    • 2021-03-16
    • 2021-06-02
    相关资源
    最近更新 更多