Adventures in Redirection III: The Revenge

After the last two entries on output redirection (here and here, if you missed them), a few folks said, “Zach, this is a Cocoa blog; what’s up with all the crazy C++ junk?” Fair enough. The main reason is that this was what I needed for the particular project I’m working on. Another reason is that C++ is what I did (a lot) in my previous life, so hopefully you’ll forgive me if I occasionally run home to mama. :)

In any case, the core code is plain ole C, so I thought, why not make a Cocoa version also? So here we go. The walkthrough is basically the same as the original C++ version, so I won’t repeat it here.

@interface StandardOutputRedirector : NSObject
{
    int redirectionPipe[2];
    int oldStandardOutput;
    int oldStandardError;
    BOOL redirecting;
    NSMutableString* redirectedOutput;
}
- (void) startRedirecting;
- (void) stopRedirecting;
- (NSString*) output;
- (void) clearOutput;
@end

@implementation StandardOutputRedirector

enum { READ, WRITE };

#pragma mark - Memory Management

- (id) init
{
    if( (self = [super init]) ) {
        redirectedOutput = [[NSMutableString alloc] init];

        if( pipe( redirectionPipe ) != -1 ) {
            oldStandardOutput = dup( fileno(stdout) );
            oldStandardError = dup( fileno(stderr) );
        }
        setbuf( stdout, NULL );
        setbuf( stderr, NULL );
    }

    return self;
}

- (void) dealloc
{
    if( redirecting  ) {
        [self stopRedirecting];
    }

    if( oldStandardOutput > 0 ) {
        close( oldStandardOutput );
    }
    if( oldStandardError > 0 ) {
        close( oldStandardError );
    }
    if( redirectionPipe[READ] > 0 ) {
        close( redirectionPipe[READ] );
    }
    if( redirectionPipe[WRITE] > 0 ) {
        close( redirectionPipe[WRITE] );
    }

    [redirectedOutput release];

    [super dealloc];
}

#pragma mark - Public methods

- (void) startRedirecting
{
    if( redirecting ) return;

    dup2( redirectionPipe[WRITE], fileno(stdout) );
    dup2( redirectionPipe[WRITE], fileno(stderr) );
    redirecting = true;
}

- (void) stopRedirecting
{
    if( !redirecting ) return;

    dup2( oldStandardOutput, fileno(stdout) );
    dup2( oldStandardError, fileno(stderr) );
    redirecting = false;
}

- (NSString*) output
{
    const size_t bufferSize = 4096;
    char buffer[bufferSize];
    fcntl( redirectionPipe[READ], F_SETFL, O_NONBLOCK );
    ssize_t bytesRead = read( redirectionPipe[READ], buffer, bufferSize - 1 );
    while( bytesRead > 0 ) {
        buffer[bytesRead] = 0;
        NSString* tempString = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
        [redirectedOutput appendString:tempString];
        bytesRead = read( redirectionPipe[READ], buffer, bufferSize );
    }

    return [NSString stringWithFormat:@"%@", redirectedOutput];
}

- (void) clearOutput
{
    [redirectedOutput setString:@""];
}

@end

One thing I would note on the Cocoa version is that, this is intended for Mac OS X. I’ve not tried it on iOS, I’ve no idea if it would work, and I actually can’t think of a reason why you’d want to do it on iOS.

This Cocoa version (along with the original C++ implementation) is included with the sample project on github, here.