开发者

NSTask character output to NSTextField unbuffered as continuous stream

开发者 https://www.devze.com 2023-03-18 06:19 出处:网络
What I want to accomplish is to start a command line (CL) task (wrapped NSTask) and pipe (NSPipe) the character output through an NSTextField label in my UI, in real-time as a stream of characters. Th

What I want to accomplish is to start a command line (CL) task (wrapped NSTask) and pipe (NSPipe) the character output through an NSTextField label in my UI, in real-time as a stream of characters. The purpose of the textfield is not to capture the output in any way, or even allow it to be read. It’s just to display it, partly as a UI decoration and partly as a kind of progress indicator. I want the user to see a stream of characters just flowing by (fast) as开发者_StackOverflow中文版 the CL task does its work.

I know how to wrap the CL task in an NSTask and get its output by setting [task setStandardOutput:outputPipe] and then read from that output with an NSFileHandle. And I think I know how to do what I want the "hard" way using one of the NSFileHandle reading methods and synchronously chopping the output into chunks and displaying those chunks one-by-one in the text field. But I’m hoping there might be some light-weight way that I haven’t thought of to sort of blast the raw ascii characters from stdout into the text field in real-time.

Anyone have an idea?

EDIT: Here is some working code based on @Peter Hosey's answer. It is doing what i want, but I don't know if I thoroughly grokked Peter's concept or if I am doing anything wonky in here, so please feel free to comment. Thanks again Peter!

notes on this code:

1) changing the scheduledTimerWithTimeInterval in the init from .001 to .005 is an interesting visual range for the text scrolling effect.

2) the label that i am using was just a simple text label created on my UI in interface builder. for my purposes, i didn't need to do the second part of Peter's answer with the right justified attributed string. i just set the text label's justification in interface builder.

@interface MyWrapper : NSObject

@property (assign) NSMutableData *_outputData;
@property (assign) NSFileHandle *_fileHandle;
@property (assign) IBOutlet NSTextField *label;
@property (assign) NSTimer *_timer;

-(void) readData:(NSNotification *)notification;
-(void) displayOutput;
-(void) doIt;

@end

@implementation MyWrapper

@synthesize _outputData, _fileHandle, label, _timer;

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

      [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector( readData: )
                                                   name:NSFileHandleReadCompletionNotification 
                                                 object:nil];
      _outputData = [[NSMutableData alloc] initWithCapacity:300];
      _timer = [NSTimer scheduledTimerWithTimeInterval:.001 
                                               target:self 
                                             selector:@selector(displayOutput) 
                                             userInfo:nil 
                                              repeats:YES];
    }

    return self;
}
- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];  
  [_timer invalidate];
  [super dealloc];
}

-(void) readData:(NSNotification *)notification {

  if( [notification object] != _fileHandle )
    return;

  [_outputData appendData:[[notification userInfo] 
          objectForKey:NSFileHandleNotificationDataItem]];

  [_fileHandle readInBackgroundAndNotify];
}

-(void) displayOutput {

  if ([_outputData length] == 0) {
    return;
  }

  NSString *labelText = [label stringValue];
  NSData *nextByte;
  NSString *nextChar;

  // pull first character off of the outputData
  nextByte = [_outputData subdataWithRange:NSMakeRange(0, 1)];
  nextChar = [[NSString alloc]initWithData:nextByte
                                   encoding:NSASCIIStringEncoding];

  // get rid of first byte of data
  [_outputData replaceBytesInRange:NSMakeRange(0, 1) withBytes:NULL length:0];

  if (! [nextChar isEqualToString:@"\n"]) {
    if ([labelText length] > 29) {
      labelText = [labelText substringFromIndex:1];
    }

    labelText = [labelText stringByAppendingString:nextChar];
    [label setStringValue:labelText];
  }  
}

-(void)doIt {

  NSTask *theTask = [[NSTask alloc] init];
  NSPipe *outPipe =[NSPipe pipe];
  //write output to outputData in background
  _fileHandle = [outPipe fileHandleForReading];
  [_fileHandle readInBackgroundAndNotify];

  [theTask setLaunchPath:@"path/to/executable"];
  [theTask setStandardOutput:outPipe];
  [theTask setStandardError:[NSPipe pipe]];
  [theTask launch];
  [theTask waitUntilExit];  
}

@end


Asynchronous reading of the file handle, a timer, an NSMutableData that you limit to a fixed number of bytes (let's say 300) by keeping only the last bytes and deleting old bytes, and right justification in the text field.

For the last part, you'll need to make a mutable copy of the default paragraph style, set its alignment to right justification, and set the text field's attributed string value to an attributed string that has the paragraph style as one of its attributes.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号