使用Swift将NSTask真实的输出到NSTextView

zzzyeukh  于 2023-04-19  发布在  Swift
关注(0)|答案(4)|浏览(134)

我正在使用NSTask运行rsync,我希望状态显示在窗口内滚动视图的文本视图中。现在我有这样的:

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中运行应用程序时打印出来的内容,并将其放入文本视图中。有没有办法做到这一点?

xqnpmsa8

xqnpmsa81#

从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: .utf8) {
        // Update your view with the new text here
        print("New ouput: \(line)")
    } else {
        print("Error decoding data: \(pipe.availableData)")
    }
}
    
task.launch()

我很惊讶没有人提到这一点,因为这是一个非常简单的。

ax6ht2ek

ax6ht2ek2#

您可以使用通知从管道中异步读取。下面是一个简单的示例,演示了它是如何工作的,希望这能帮助您入门:

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是活动的(这是默认可可应用程序中的情况)。

6tqwzwtp

6tqwzwtp3#

这是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()
ac1kyiln

ac1kyiln4#

我有一个答案,我相信它比通知方法更干净,基于readabilityHandler。在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.
           }

     }
   }

}

请注意,Process必须是一个属性,因为在上面的例子中,假设命令是在后台执行的,如果它是一个局部变量,那么进程将立即被释放。感谢您的关注。

相关问题