Repeat Process() n times in Swift 3

Viewed 1149

I am making a macOS app. I have a task (a script) that I am running with Process() in Swift 3. When I press a button (button.State == NSOnState), I would like the task to repeating n times, and terminate earlier until the button is pressed again (button.State == NSOffState).

I looked up how to repeat a task, and it looks possible with a simple for loop – for i in {1..n}.

Now, the problem I am having is that it doesn't seem to be possible to call a task multiple times. When I try to call the task the second time, I get an error in the console:

[General] task already launched

Here is my code:

@IBAction func buttonPressed(_ sender: Any) {  

  let script = "for i in {1..5}; do echo \"hi\"; done; sleep 1"

  let task = Process()

  task.terminationHandler = self.commandTerminationHandler
  task.launchPath = "/bin/bash"
  task.arguments = ["-c", script]

  if button.state == NSOnState {

    task.launch() // launches task
    task.waitUntilExit() // waits until task has been completed (about 1 second)
    task.terminate() // (should) terminate the task. (The console error occurs with or without this line)
    task.launch() // tries launching the task again, but this results in the console error. 

    print("The task was launched twice")

  } else {

    // task.terminate()

  }

}

I googled this error, and found [this][https://forums.macrumors.com/threads/nstask-not-terminating.1617855/#post-17677541]:

The error isn't that the task is still running. It's that the task has already run and completed, and can't be started again. You'll need to create a new NSTask object to run the task again.

So I need to make a new NSTask (or Process as of Swift 3) and keep making new ones to repeat the code forever. This sound very complicated (as if I'm using a workaround) and is probably inefficient.

Is there a better way to repeat a Process in Swift 3?


For the sake of completeness, I'd also like to mention that I considered using for i in {1..n} do ... done directly in script. This has one problem:

  1. It doesn't look like it's possible to stop the task when the button is pressed again. This is because if I run task.terminate(), I get the error "task not launched." The only way I can stop it is by running killall bash in my Terminal, which doesn't seem like a nice solution. To do this in Xcode, I'd need make a Process to kill bash with bash... which is strange.
3 Answers

If you set your task item to a var instead of a let you can repeatedly set task to a new process object inside your loop.

var task = Process()
task.launchPath = ....
task.arguments = [...]
task = Process()
task.launchPath = ....
task.arguements = [...]
Related