2015-11-20 70 views
2

我有一個外部控制檯應用程序(在OS X上),它將從1到100的整數序列發送到標準輸出,大約每秒一次。Swift:如何在不等待進程完成的情況下讀取子進程中的標準輸出

我斯威夫特,我需要使用該數字流來更新進度指示器。

下面是代碼,我到目前爲止有:

class MasterViewController: NSViewController { 

@IBOutlet weak var progressIndicator: NSProgressIndicator! 

override func viewDidLoad() { 
    super.viewDidLoad() 

    let task = Process() 
    task.launchPath = "/bin/sh" 
    task.arguments = ["-c", "sleep 1; echo 10 ; sleep 1 ; echo 20 ; sleep 1 ; echo 30 ; sleep 1 ; echo 40; sleep 1; echo 50; sleep 1; echo 60; sleep 1"] 

    let pipe = Pipe() 
    task.standardOutput = pipe 

    task.launch() 

    let data = pipe.fileHandleForReading.readDataToEndOfFile() 
    if let string = String(data: data, encoding: String.Encoding.utf8) { 
     print(string) 
    } 
} 

代碼工作 - 也就是說,它讀取命令行實用程序的輸出,並修改進度指示器相應地,但它使所有的在之後更改實用程序退出(並使得我的用戶界面在此期間等待)。

我該如何設置它,以便它從後臺應用程序讀取輸出並實時更新進度指示器?

UPDATE

以供將來參考,這是我如何得到它到底(現在更新的斯威夫特3)工作:現在

class ViewController: NSViewController { 

    @IBOutlet weak var progressIndicator: NSProgressIndicator! 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     // Do any additional setup after loading the view. 


     let task = Process() 
     task.launchPath = "/bin/sh" 
     task.arguments = ["-c", "sleep 1; echo 10 ; sleep 1 ; echo 20 ; sleep 1 ; echo 30 ; sleep 1 ; echo 40; sleep 1; echo 50; sleep 1; echo 60; sleep 1"] 

     let pipe = Pipe() 
     task.standardOutput = pipe 
     let outHandle = pipe.fileHandleForReading 
     outHandle.waitForDataInBackgroundAndNotify() 

     var progressObserver : NSObjectProtocol! 
     progressObserver = 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 = String(data: data, encoding: String.Encoding.utf8) { 
        if let newValue = Double(str.trimEverything) { 
         self.progressIndicator.doubleValue = newValue 
        } 
       } 
       outHandle.waitForDataInBackgroundAndNotify() 
      } else { 
       // That means we've reached the end of the input. 
       NotificationCenter.default.removeObserver(progressObserver) 
      } 
     } 

     var terminationObserver : NSObjectProtocol! 
     terminationObserver = NotificationCenter.default.addObserver(
      forName: Process.didTerminateNotification, 
      object: task, queue: nil) 
     { 
      notification -> Void in 
      // Process was terminated. Hence, progress should be 100% 
      self.progressIndicator.doubleValue = 100 
      NotificationCenter.default.removeObserver(terminationObserver) 
     } 

     task.launch() 

    } 

    override var representedObject: Any? { 
     didSet { 
     // Update the view, if already loaded. 
     } 
    } 

} 


// This is just a convenience extension so that I can trim 
// off the extra newlines and white spaces before converting 
// the input to a Double. 
fileprivate extension String { 
    var trimEverything: String { 
     return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 
    } 
} 

,進度條發展到60%,然後一旦兒童進程完成,就跳到100%。

回答

3

您正在主線程上同步讀取,因此直到函數返回到主循環纔會更新UI。

有(至少)兩種可能的方法來解決這個問題:

  • 從管道做閱讀在後臺線程(例如,通過其派遣到後臺排隊 - 但不要忘記 再次將UI更新分派給主線程)。
  • 使用通知從管道異步讀取(有關示例,請參閱 Real time NSTask output to NSTextView with Swift)。
+1

請務必讓子進程將其標準輸出設置爲行緩衝或在每行之後顯式刷新它。否則,它可能會將其輸出保存在內部緩衝區中,而不是實際寫入管道。 –

+0

是的,第二個選項做到了。 – AthanasiusOfAlex

相關問題