NSTask Tutorial for OS X

In this OS X NSTask tutorial, learn how to execute another program on your machine as a subprocess and monitor its execution state while your main program continues to run. By Warren Burton.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Using Outputs

Okay, you’re pretty well versed in passing arguments to command line programs, but what about dealing with the output of these command line programs?

To see this in action, type the following command into Terminal and hit Enter:

date

You should see a message produced that looks something like this:

Fri 19 Feb 2016 17:48:15 GMT

date tells you the current date and time. Although it isn’t immediately obvious, the results were sent back to you on a channel called standard output.

Processes generally have three default channels for input and output:

  • standard input, which accepts input from the caller;
  • standard output, which sends output from the process back to the caller; and
  • standard error, which sends errors from the process back to the caller.

Pro tip: you’ll see these commonly abbreviated as stdin, stdout, and stderr.

There is also a pipe that allows you to redirect the output of one process into the input of another process. You’ll be creating a pipe to let your application see the standard output from the process that NSTask runs.

To see a pipe in action, ensure the volume is turned up on your computer, then type the following command in Terminal:

date | say

Hit enter and you should hear your computer telling you what time it is.

Note: The pipe character “|” on your keyboard is usually located on the back slash \ key, just above the enter/return key.

Here’s what just happened: you created a pipe that takes the standard output of date and redirects it into the standard input of say. You can also provide options to the commands that communicate with pipes, so if you would like to hear the date with an Australian accent, type the following command instead:

date | say -v karen

Its the date, mate!

You can construct some rather long chains of commands using pipes, redirecting the stdout from one command into the stdin of another. Once you get comfortable using stdin, stdout, and pipe redirects, you can do some really complicated things from the command line using tools like pipes.

Now it’s time to implement a pipe in your app.

Note: NSPipe is a Foundation class that is called Pipe in Swift 3. Most documentation will refer to NSPipe.

Open TasksViewController.swift and replace the comment that reads // TODO: Output Handling in runScript(_:) with the following code:

self.captureStandardOutputAndRouteToTextView(self.buildTask)

Next, add this function to TasksViewController.swift:

func captureStandardOutputAndRouteToTextView(_ task:Process) {
  
  //1.
  outputPipe = Pipe()
  task.standardOutput = outputPipe
  
  //2.
  outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
  
  //3.
  NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outputPipe.fileHandleForReading , queue: nil) {
    notification in
    
    //4.
    let output = self.outputPipe.fileHandleForReading.availableData
    let outputString = String(data: output, encoding: String.Encoding.utf8) ?? ""
    
    //5.
    DispatchQueue.main.async(execute: {
      let previousOutput = self.outputText.string ?? ""
      let nextOutput = previousOutput + "\n" + outputString
      self.outputText.string = nextOutput
      
      let range = NSRange(location:nextOutput.characters.count,length:0)
      self.outputText.scrollRangeToVisible(range)
      
    })
    
    //6.
    self.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
  }
  
}

This function collects the output from the external process and adds it to the GUI’s outputText field. It works as follows:

  1. Creates an Pipe and attaches it to buildTask‘s standard output. Pipe is a class representing the same kind of pipe that you created in Terminal. Anything that is written to buildTask‘s stdout will be provided to this Pipe object.
  2. Pipe has two properties: fileHandleForReading and fileHandleForWriting. These are NSFileHandle objects. Covering NSFileHandle is beyond the scope of this tutorial, but the fileHandleForReading is used to read the data in the pipe. You call waitForDataInBackgroundAndNotify on it to use a separate background thread to check for available data.
  3. Whenever data is available, waitForDataInBackgroundAndNotify notifies you by calling the block of code you register with NSNotificationCenter to handle NSFileHandleDataAvailableNotification.
  4. Inside your notification handler, gets the data as an NSData object and converts it to a string.
  5. On the main thread, appends the string from the previous step to the end of the text in outputText and scrolls the text area so that the user can see the latest output as it arrives. This must be on the main thread, like all UI and user interaction.
  6. Finally, repeats the call to wait for data in the background. This creates a loop that will continually wait for available data, process that data, wait for available data, and so on.

Build and run your application again; make sure the Project Location and Build Repository fields are set correctly, type in your target’s name and click Build.

You should see the output from the building process in your outputText field:

final window view

Stopping an NSTask

What happens if you start a build, then change your mind? What if it’s taking too long, or something else seems to have gone wrong and it’s just hanging there, making no progress? These are times when you’ll want to be able to stop your background task. Fortunately, this is pretty easy to do.

In TasksViewController.swift, add the following code to stopTask(_:)

if isRunning {
  buildTask.terminate()
}

The code above simply checks if the NSTask is running, and if so, calls its terminate method. This will stop the NSTask in its tracks.

Build and run your app, ensure all fields are configured correctly and hit the Build button. Then hit the Stop button before the build is complete. You’ll see that everything stops and no new .ipa file is created in your output directory.

Where to Go From Here?

Here is the finished NSTask example project from the above tutorial.

Congratulations, you’ve begun the process of becoming an NSTask ninja!

In one short tutorial, you’ve learned:

  • How to create NSTasks with arguments and output pipes; and
  • How to create a shell script and call it from your app!

To learn more about NSTask, check out Apple’s official NSTask Class Reference.

To learn about using command line programs in a sandboxed app see Daemons and Services Programming Guide and XPC Services API Reference.

This tutorial only dealt with working with stdout with NSTask, but you can use stdin and stderr as well! To practice your new skills, try working with these.

I hope you enjoyed this NSTask tutorial and that you find it useful in your future Mac OS X apps. If you have any questions or comments, please join the forum discussion below!

Contributors

Over 300 content creators. Join our team.