The Turnstone's Bill

A Simpler NSTask

Standard Cocoa provides the NSTask class to make calls out to the operating system. This is really really useful because the operating system is packed with command-line tools that can do alot more than straight Cocoa. Actually using NSTask though can be a bit of a pain. In order to look at the output of your task you’ll need to watch it’s standardOutput, and if the task takes an appreciable amount of time you will probably need to monitor this output on another thread. This isn’t all that hard, but it isn’t all that easy either. I realised there was a better way when I started using the NSURLConnection class, which is a Class whose API is designed specifically for long running jobs. Monitoring an NSURLConnection is as simple as making yourself its delegate and implementing a couple of methods.

Enter MFTask, which is a wrapper class for NSTask that provides some delegate methods for monitoring output, termination and launch. In my experience, it’s much easier to use than a raw NSTask. In DropSync I also tend to run lots of MFTask’s so I’ve also written a very basic queuing class specifically designed to handle running a queue of MFTask’s.

The source code for MFTask is available on bitbucket and comes with a small sample application which should demonstrate how to use it.

Let’s say you wanted to setup and run an NSTask, and monitor it’s output. Using MFTask you would first setup the task just like a normal NSTask. For example, here’s a task to find all the xml documents on your system. Setting up the task you would simply set its launch path and arguments as you would a normal NSTask;

Setting up Task Arguments
MFTask *taskObject = [[MFTask alloc] init];
[taskObject setLaunchPath:@"/usr/bin/find"];
NSArray *argvals = [NSArray arrayWithObjects:@"/",@"-name",@"*.xml",nil];
[taskObject setArguments:argvals];

In addition, an MFTask requires a delegate, so in order to monitor output of the task you will need a delegate which implements the following methods

Delegate Methods
- (void) taskDidRecieveData:(NSData*) theData fromTask:(MFTask*)task;
- (void) taskDidRecieveErrorData:(NSData*) theData fromTask:(MFTask*)task;
- (void) taskDidTerminate:(MFTask*) theTask;
- (void) taskDidRecieveInvalidate:(MFTask*) theTask;
- (void) taskDidLaunch:(MFTask*) theTask;

The methods taskDidRecieveData and taskDidReceiveErrorData provide a means of monitoring the standard output and standard error streams respectively. Simply write these methods to deal with data coming off the task. For example, here is a delegate method that simply dumps the output to NSLog (not very useful I know … but it illustrates the point).

Receiving Data in a Callback
- (void) taskDidRecieveData:(NSData*) theData fromTask:(MFTask*) task {
  NSString *stringRep = [[NSString alloc]; initWithData:theData encoding:NSASCIIStringEncoding];

Finally, to launch the task you set its delegate to the class where you defined the delegate methods and then call launch. For example if your delegate class was called MFTaskDelegate;

Setting a Delegate
MFTaskDelegate *taskDelegate = [[MFTaskDelegate alloc]; init];
[taskObject setDelegate:taskDelegate];
[taskObject launch];

Now your task should be off and running and feeding data to your delegate.

If the task terminates normally your delegate will recieve a taskDidTerminate message.
If you need to terminate a task simply send it a terminate message as you would a normal NSTask object, and the task will go about terminating and will tell the delegate when it actually terminates by sending it a taskDidTerminate message.
If you want to terminate a task but don’t want to recieve any messages from you can use the invalidate method which not only terminates the task, but tells it not to send any further messages to its delegate. This is useful if your delegate might go away for some reason before the task is finished.

That’s it. I hope you find MFTask to be useful.