tags:

views:

41

answers:

1

I am working on a GUI (Cocoa) for a command-line tool to make it more accessible to people. Despite it being a GUI, I would like to display the output to an NSTextView. The problem is that the output is large and the analysis the tool carries out can take hours/days.

Normally, when working with NSTask and NSPipe, the output is displayed only after the task is completely finished (which can take a long time). What I want to do is split the output up and display it gradually (updating every minute for example).

So far I have placed the processing of the data in a separate thread:

[NSThread detachNewThreadSelector:@selector(processData:) toTarget:self withObject:raxmlHandle];

- (void)processData:(id)sender {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *startString = [results string];
    NSString *newString = [[NSString alloc] initWithData:[raxmlHandle readDataToEndOfFile] encoding:NSASCIIStringEncoding];
    [results setString:[startString stringByAppendingString:newString]];
    [startString release];
    [newString release];
    [pool release];
}

All this is still a bit of voodoo to me and I am not exactly sure how to deal with this challenge.

Do you have any suggestions or recommendations?

Thanks!

+3  A: 

You need to use a notification provided by NSFileHandle.

First, add yourself as an observer to the NSFileHandleReadCompletionNotification

[[NSNotificationCenter defaultCenter] addObserver:self  
                                         selector:@selector(outputReceived:) 
                                             name:NSFileHandleReadCompletionNotification
                                           object:nil];

Then, prepare a task, call readInBackgrounAndNotify of the file handle of the pipe, and launch the task.

NSTask*task=[[NSTask alloc] init];
[task setLaunchPath:...];

NSPipe*pipe=[NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardError:pipe];

// this causes the notification to be fired when the data is available
[[pipe fileHandleForReading] readInBackgroundAndNotify];  

[task launch];

Now, to actually receive the data, you need to define a method

-(void)outputReceived:(NSNotification*)notification{
    NSFileHandle*fh=[notification object];
    // it might be good to check that this file handle is the one you want to read
    ...

    NSData*d=[[aNotification userInfo] objectForKey:@"NSFileHandleNotificationDataItem"];
    ... do something with data ...
}

You might want to read Notification Programming Topics to understand what is going on.

Yuji
Thanks a lot, Yuji, for taking the time to write down your answer. I'm going to dive right in!
bare_nature
Does anyone know how to have more control over the NSPipe's output size? In other words, with the above approach (Yuji's approach), the data is purged in large chunks and this makes it less user friendly. I'm looking for a solution in which each line of output can be displayed separately (more like the Terminal or the Console work). Any suggestions?
bare_nature
Read this SO discussion: http://stackoverflow.com/questions/1000674/turn-off-buffering-in-pipe The problem is that the stdio in the sending side changes its behavior automatically when it emits the data to a pipe. You need to trick it to think it's writing to a tty. There should be a way to implement tty in Cocoa, too, but I don't know how. I'm sure it can be found in the source code of iTerm, http://iterm.sourceforge.net/ .
Yuji