使用Swift将NSTask实时输出到NSTextView
我正在使用NSTask运行rsync,我希望状态显示在窗口内滚动视图的文本视图中。现在我有这个:使用Swift将NSTask实时输出到NSTextView,swift,nstextview,nstask,Swift,Nstextview,Nstask,我正在使用NSTask运行rsync,我希望状态显示在窗口内滚动视图的文本视图中。现在我有这个: let pipe = NSPipe() task2.standardOutput = pipe task2.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output: String = NSString(data: data, encoding: NSASCIIStringEncoding)! as
let pipe = NSPipe()
task2.standardOutput = pipe
task2.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: NSASCIIStringEncoding)! as String
textView.string = output
这是关于传输的一些统计信息,但我想实时获取输出,比如在Xcode中运行应用程序时打印出来的get,并将其放入文本视图。有办法做到这一点吗?(有关Swift 3/4的更新,请参阅。)
您可以使用通知从管道异步读取。
这里有一个简单的例子来演示它是如何工作的,希望
帮助您开始:
let task = NSTask()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "echo 1 ; sleep 1 ; echo 2 ; sleep 1 ; echo 3 ; sleep 1 ; echo 4"]
let pipe = NSPipe()
task.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.waitForDataInBackgroundAndNotify()
var obs1 : NSObjectProtocol!
obs1 = NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification,
object: outHandle, queue: nil) { notification -> Void in
let data = outHandle.availableData
if data.length > 0 {
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) {
print("got output: \(str)")
}
outHandle.waitForDataInBackgroundAndNotify()
} else {
print("EOF on stdout from process")
NSNotificationCenter.defaultCenter().removeObserver(obs1)
}
}
var obs2 : NSObjectProtocol!
obs2 = NSNotificationCenter.defaultCenter().addObserverForName(NSTaskDidTerminateNotification,
object: task, queue: nil) { notification -> Void in
print("terminated")
NSNotificationCenter.defaultCenter().removeObserver(obs2)
}
task.launch()
您可以附加接收到的数据,而不是print(“got output:\(str)”)
字符串添加到文本视图
上面的代码假设runloop处于活动状态(即
在默认Cocoa应用程序中)。自macOS 10.7以来,
NSPipe
上还有readabilityHandler
属性,可用于设置新数据可用时的回调:
let task = NSTask()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "echo 1 ; sleep 1 ; echo 2 ; sleep 1 ; echo 3 ; sleep 1 ; echo 4"]
let pipe = NSPipe()
task.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.readabilityHandler = { pipe in
if let line = String(data: pipe.availableData, encoding: NSUTF8StringEncoding) {
// Update your view with the new text here
print("New ouput: \(line)")
} else {
print("Error decoding data: \(pipe.availableData)")
}
}
task.launch()
我很惊讶没有人提到这一点,因为这要简单得多。这是Martin在最新版本Swift中对上述答案的更新版本
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "echo 1 ; sleep 1 ; echo 2 ; sleep 1 ; echo 3 ; sleep 1 ; echo 4"]
let pipe = Pipe()
task.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.waitForDataInBackgroundAndNotify()
var obs1 : NSObjectProtocol!
obs1 = 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 = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
print("got output: \(str)")
}
outHandle.waitForDataInBackgroundAndNotify()
} else {
print("EOF on stdout from process")
NotificationCenter.default.removeObserver(obs1)
}
}
var obs2 : NSObjectProtocol!
obs2 = NotificationCenter.default.addObserver(forName: Process.didTerminateNotification,
object: task, queue: nil) { notification -> Void in
print("terminated")
NotificationCenter.default.removeObserver(obs2)
}
task.launch()
我有一个答案,我相信它比通知方法更清晰,基于可读性handler。这是Swift 5:
class ProcessViewController: NSViewController {
var executeCommandProcess: Process!
func executeProcess() {
DispatchQueue.global().async {
self.executeCommandProcess = Process()
let pipe = Pipe()
self.executeCommandProcess.standardOutput = pipe
self.executeCommandProcess.launchPath = ""
self.executeCommandProcess.arguments = []
var bigOutputString: String = ""
pipe.fileHandleForReading.readabilityHandler = { (fileHandle) -> Void in
let availableData = fileHandle.availableData
let newOutput = String.init(data: availableData, encoding: .utf8)
bigOutputString.append(newOutput!)
print("\(newOutput!)")
// Display the new output appropriately in a NSTextView for example
}
self.executeCommandProcess.launch()
self.executeCommandProcess.waitUntilExit()
DispatchQueue.main.async {
// End of the Process, give feedback to the user.
}
}
}
}
请注意,该进程必须是一个属性,因为在上面的示例中,如果该命令在后台执行,那么如果该进程是一个局部变量,它将立即被释放。感谢您的关注。我在这段代码中发现了一个编译错误,说明了这个错误:变量“obs1”在初始化之前被闭包捕获(对于“obs2”也是如此)。我将removeObserver行移出了闭包(在waitUntilExit调用之后)。对斯威夫特来说很陌生,所以为无知道歉,但是。。为什么?@ticktock:你说得对!这个错误没有出现在我的命令行测试应用程序中,但是如果代码被放入一个方法中,它就会发生。将obs1/2声明为“隐式展开可选”
NSProtocol代码>解决问题,请参阅更新的答案谢谢你的反馈!有时我注意到,使用通知有时无法将所有数据写入管道,这非常令人讨厌@Kametrixom解决方案似乎更可靠(也更简单)。@BenStock:谢谢您的编辑建议。然而,一个Swift 3版本已经作为另一个答案发布,链接到该版本对我来说似乎更合适。嗨,谢谢你的答案,-c的意思是什么?这更简单——我想没有人提到它,因为它在文档中很难找到。非常感谢你outHandle.readabilityHandler
你救了我一天!