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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
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.
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
.
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:
- Creates an
Pipe
and attaches it tobuildTask
‘s standard output.Pipe
is a class representing the same kind of pipe that you created inTerminal
. Anything that is written tobuildTask
‘sstdout
will be provided to thisPipe
object. -
Pipe
has two properties:fileHandleForReading
andfileHandleForWriting
. These areNSFileHandle
objects. CoveringNSFileHandle
is beyond the scope of this tutorial, but thefileHandleForReading
is used to read the data in the pipe. You callwaitForDataInBackgroundAndNotify
on it to use a separate background thread to check for available data. - Whenever data is available,
waitForDataInBackgroundAndNotify
notifies you by calling the block of code you register withNSNotificationCenter
to handleNSFileHandleDataAvailableNotification
. - Inside your notification handler, gets the data as an
NSData
object and converts it to a string. - 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. - 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:
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!