NSTask character output to NSTextField unbuffered as continuous stream

╄→гoц情女王★ 提交于 2019-12-11 03:32:50

问题


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

回答1:


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.



来源:https://stackoverflow.com/questions/6635086/nstask-character-output-to-nstextfield-unbuffered-as-continuous-stream

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!