2

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

Lightbeard
  • 4,011
  • 10
  • 49
  • 59
  • 1
    What does the program do after writing the string? Does it return the run loop? – Ken Thomases Oct 04 '15 at 19:16
  • The code is in a method that is run when a UI button is selected. I'm embarrassed to say I don't know what a run loop is - can you elaborate? – Lightbeard Oct 04 '15 at 20:58
  • 2
    [Apple's explanation of run loops.](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html) The main event loop of an app is built on top of a run loop, so it sounds like you're probably good in that respect. I brought it up because the docs for `-waitForDataInBackgroundAndNotify` say "You must call this method from a thread that has an active run loop." If you had, for example, blocked while waiting for the notification, that would not have worked. – Ken Thomases Oct 04 '15 at 22:15
  • I found the problem thanks to your question. I had forgotten that I was running the code above using an NSThread. When run from the main thread the code above works! – Lightbeard Oct 04 '15 at 22:26

1 Answers1

1

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..)

canhazbits
  • 1,664
  • 1
  • 14
  • 19