【问题标题】:Real time NSTask output to NSTextView with Swift使用 Swift 将 NSTask 实时输出到 NSTextView
【发布时间】:2015-06-15 10:15:44
【问题描述】:

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

【问题讨论】:

    标签: swift nstextview nstask


    【解决方案1】:

    (有关 Swift 3/4 的更新,请参阅 Patrick F.'s answer。)

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

    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 应用程序中)。

    【讨论】:

    • 我得到了一个编译错误,这段代码说明了错误:变量'obs1'在被初始化之前被闭包捕获(对于'obs2'也是如此)。我将 removeObserver 线移出闭包(在 waitUntilExit 调用之后)。对 swift 非常陌生,因此为无知道歉,但是.. 为什么?
    • @ticktock:你是对的!该错误在我的命令行测试应用程序中没有发生,但如果将代码放入方法中,它确实会发生。将 obs1/2 声明为“隐式展开可选”NSProtocol! 可以解决问题,请参阅更新的答案。 – 感谢您的反馈!
    • 有时我注意到使用通知有时无法将所有数据写入管道 - 非常烦人。 @Kametrixom 解决方案似乎更可靠(也更简单)。
    • @BenStock:感谢您的编辑建议。但是,Swift 3 版本已经作为另一个答案发布,链接到那个对我来说似乎更合适。
    • 你好,谢谢你的回答,-c是什么意思?
    【解决方案2】:

    从 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()
    

    我很惊讶没有人提到这一点,因为它要简单得多。

    【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      我有一个我认为比通知方法更干净的答案,基于 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.
                 }
      
           }
         }
      
      }
      

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

      【讨论】:

        猜你喜欢
        • 2011-10-19
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多