objective-cmacoscocoanspipe

Using NSPipe for interactive commands


I'm launching some interactive process inside my OS X app I want to be able to read and write from pipes.

For example: Launching the process will wait for user to type command. When user is finished (aka pressed enter), process will return something and then again wait for user.

For now, I'm using NSPipe class for communication, but the problem is when method writeData: is called, I have to call closeFile in order to get notification NSFileHandleDataAvailableNotification.

Complete code (with changed folder paths) is this

dispatch_queue_t taskQueue =
   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
  dispatch_async(taskQueue, ^{

    task = [[NSTask alloc] init];
    [task setStandardOutput: [NSPipe pipe]];
    [task setStandardInput: [NSPipe pipe]];
    [task setStandardError: [task standardOutput]];
    [task setLaunchPath: @"/Users/..."];
    [task setArguments:@[@"--interaction"]];

    [[[task standardOutput] fileHandleForReading] waitForDataInBackgroundAndNotify];

    [[NSNotificationCenter defaultCenter]
        addObserverForName:NSFileHandleDataAvailableNotification
                    object:[[task standardOutput] fileHandleForReading]
                     queue:nil
                usingBlock:^(NSNotification *notification){
        NSData *output = [[[task standardOutput] fileHandleForReading] availableData];
        NSString *outStr = [[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding];
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"avaliable data: %@", outStr);
            NSString * message = @"IOTCM \"/Users/.../Demo.agda\" None Indirect ( Cmd_show_version )";
            [[[task standardInput] fileHandleForWriting] 
                   writeData:[message dataUsingEncoding:NSUTF8StringEncoding]];
        });
        [[[task standardOutput] fileHandleForReading] waitForDataInBackgroundAndNotify];
    }];

    [task launch];
    [task waitUntilExit];
});

Note that I get first notification (process is responding), but no notification comes after writeData: gets called.

How to achieve communication, that:

  1. gets launched (and stays launched through app lifecycle)

  2. writing and reading is supported


Solution

  • You need to append a newline to any commands you send to the pipe, in the same way you would interactively. Newlines flush stream buffers and are, in general, the "go" part of the command.

    I cannot find any reference to re-enforce this answer, however.