Swift:如何在不等待进程完成的情况下读取子进程中的标准输出 [英] Swift: How to read standard output in a child process without waiting for process to finish
问题描述
我有一个外部控制台应用程序(在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:
- 从在后台线程上进行管道传输(例如,通过将其分派到后台队列–但不要忘记
再次将UI更新分派到主线程)。 - 使用通知来从管道异步读取(请参阅
实时将NSTask输出到NSTextView无线例如Swift )。
- 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屋!