0

I'm new to objective-C, so please forgive me if I'm missing something. But we all have to start somewhere :)

I have a snippet of code I got from another open source project that executes a command and passes the result to another method. What I need to do is listen for each new line printed to stdout and do something with each line.

The code snippet I'm working with is the following:

    NSMutableArray *args  = [NSMutableArray array];

    NSString *input = [details valueForKey:@"input"];

    for (NSString *i in [input componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]) {
        [args addObject:i];
    }

    NSTask *scriptTask = [NSTask new];
    NSPipe *outputPipe = [NSPipe pipe];

    if ([_NSFileManager() isExecutableFileAtPath:scriptPath] == NO) {
        NSArray *chmodArguments = @[@"+x", scriptPath];

        NSTask *chmod = [NSTask launchedTaskWithLaunchPath:@"/bin/chmod" arguments:chmodArguments];

        [chmod waitUntilExit];
    }

    [scriptTask setStandardOutput:outputPipe];
    [scriptTask setLaunchPath:scriptPath];
    [scriptTask setArguments:args];

    NSFileHandle *filehandle = [outputPipe fileHandleForReading];

    [scriptTask launch];
    [scriptTask waitUntilExit];

    NSData *outputData = [filehandle readDataToEndOfFile];

    NSString *outputString  = [NSString stringWithData:outputData encoding:NSUTF8StringEncoding];

    if (NSObjectIsNotEmpty(outputString)) {
        [self.world.iomt inputText:outputString command:IRCPrivateCommandIndex("privmsg")];
    }

So, rather than waiting for the process to complete then doing something with the result, I need to wait for each new line that gets printed by the command to stdout.

My background is mostly in web dev, so I guess if you were using Node.js and event emitters my aim would look similar to the following:

task = new Task("ls");

task.addListener("newline", function(data) {
    somethingElse("sendmsg", data);
});

task.run();

Hopefully you understand what I'm tying to achieve. Thanks!

1 Answers1

0

What you can do is add an observer to the NSFileHandle to notify you when it reads something.

example:

[fileHandler readInBackgroundAndNotify];
//Need to set NSTask output pipes
[self.task setStandardOutput: outputPipe];
[self.task setStandardError: outputPipe];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(readFromTask:) name:NSFileHandleReadCompletionNotification object:fileHandler];

-(void)readFromTask:(NSNotification *)aNotifcation;
    {
        NSData *data = [[aNotifcation userInfo] objectForKey: NSFileHandleNotificationDataItem];
        if (data != 0) {
            NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

                // Do something with the text

            [text release];
            [[aNotifcation object] readInBackgroundAndNotify];  
        }
    }

Then there's a notififcation you can add to the task to indicate when its completed so you can do clean up.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskEnded:) name:NSTaskDidTerminateNotification object:task];



- (void)taskEnded:(NSNotification *) aNotifcation
{
    NSData *data = [[aNotifcation userInfo] objectForKey: NSFileHandleNotificationDataItem];
    if (data != 0) {
        NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    [text release];

}

[self.task release];

}

So from your code, you could remove:

 [scriptTask waitUntilExit];

And move the data handling into the notifications.

NSData *outputData = [filehandle readDataToEndOfFile];

NSString *outputString  = [NSString stringWithData:outputData encoding:NSUTF8StringEncoding];

if (NSObjectIsNotEmpty(outputString)) {
    [self.world.iomt inputText:outputString command:IRCPrivateCommandIndex("privmsg")];
}

That's a rough idea, there's a good post at

http://www.cocoabuilder.com/archive/cocoa/306145-nstask-oddity-with-getting-stdout.html

Colin Swelin
  • 841
  • 7
  • 7