Uscita NSTask in tempo reale a NSTextView con Swift

Sto usando un NSTask per eseguire rsync e mi piacerebbe che lo stato si visualizzasse nella visualizzazione di testo di una vista di scorrimento all’interno di una finestra. In questo momento ho questo:

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 

E questo mi ottenere è il alcune delle statistiche circa il trasferimento, ma mi piacerebbe per ottenere il risultato in tempo reale, come quello che get stampate quando ho eseguito l’applicazione in Xcode, e metterlo nella vista testo. C’è un modo per fare questo?

(Vedi la risposta di Patrick F. per un aggiornamento a Swift 3/4.)

Si può leggere in modo asincrono da un tubo, utilizzando le notifiche. Ecco un semplice esempio che dimostra come funziona, si spera che ti aiuti a iniziare:

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

Invece di print("got output: \(str)") è ansible aggiungere la stringa ricevuta per la visualizzazione del testo.

Il codice sopra presuppone che un runloop è attivo (che è il caso in un’applicazione Cocoa predefinita).

Dal MacOS 10.7, c’è anche la readabilityHandler proprietà su NSPipe che può essere utilizzato per impostare la richiamata per quando sono disponibili nuovi dati:

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

Sono sorpreso che nessuno abbia menzionato questo, perché è molto più semplice.

Questa è la versione di aggiornamento della risposta di Martin sopra per l’ultima versione di 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()