iosobjective-cnsnotificationcenternsfilehandle

Can't get NSFileHandleDataAvailableNotification to fire for stdout pipe


We need to capture stdout in iOS because we are using an open source project that communicates via stdin/stdout

This works:

NSPipe *pipe = [NSPipe pipe];
NSFileHandle *readh = [pipe fileHandleForReading];

dup2([[pipe fileHandleForWriting] fileDescriptor], fileno(stdout));

[@"Hello iOS World!" writeToFile:@"/dev/stdout" atomically:NO encoding:NSUTF8StringEncoding error:nil];

NSData *data = [readh availableData];
NSLog(@"%d", [data length]);
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@%@", @"stdout captured:", str);

The console prints:

2015-10-04 12:03:29.396 OpeningExplorer[857:42006] 16
2015-10-04 12:03:29.397 OpeningExplorer[857:42006] stdout captured:Hello iOS World!

However the above example blocks. Why doesn't the following async code work?

    NSPipe *pipe = [NSPipe pipe];
    NSFileHandle *readh = [pipe fileHandleForReading];

    dup2([[pipe fileHandleForWriting] fileDescriptor], fileno(stdout));

    [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                        object:readh
                                                       queue:[NSOperationQueue mainQueue]
                                                    usingBlock:^(NSNotification *note) {
        NSFileHandle *fileHandle = (NSFileHandle*) [note object];
        NSLog(@"inside listener");
        NSData *data = [fileHandle availableData];
        NSLog(@"%d", [data length]);
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@%@", @"stdout captured:", str);

    }];
    [readh waitForDataInBackgroundAndNotify];

    [@"Hello iOS World!" writeToFile:@"/dev/stdout" atomically:NO encoding:NSUTF8StringEncoding error:nil];

The notification code block doesn't seem to be called - the console doesn't output anything


Solution

  • From Apple's documentation on waitForDataInBackgroundAndNotify: "You must call this method from a thread that has an active run loop."

    That means you can't call this from within an NSOperationQueue or an NSThread. So make sure this code is running from your main UI thread, not a background thread.

    (Posted in case anyone is lazy like me and doesn't want to parse through the comments to find the answer..)