Salida NSTask en tiempo real a NSTextView con Swift

Estoy usando un NSTask para ejecutar rsync, y me gustaría que el estado aparezca en la vista de texto de una vista de desplazamiento dentro de una ventana. En este momento tengo esto:

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 

Y esas son algunas de las estadísticas sobre la transferencia, pero me gustaría obtener el resultado en tiempo real, como lo que se imprime cuando ejecuto la aplicación en Xcode, y ponerlo en la vista de texto. ¿Hay alguna forma de hacer esto?

(Consulte la respuesta de Patrick F. para una actualización de Swift 3/4).

Puede leer de forma asincrónica desde una tubería, usando notificaciones. Aquí hay un ejemplo simple que demuestra cómo funciona, con suerte eso lo ayuda a comenzar:

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

En lugar de print("got output: \(str)") puede agregar la cadena recibida a su vista de texto.

El código anterior asume que un runloop está activo (que es el caso en una aplicación Cocoa predeterminada).

Desde macOS 10.7, también existe la propiedad readabilityHandler en NSPipe que puede utilizar para establecer una callback para cuando haya nuevos datos disponibles:

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

Me sorprende que nadie haya mencionado esto, ya que es mucho más simple.

Esta es la versión de actualización de la respuesta de Martin anterior para la última versión de 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()