Вывод символов NSTask в NSTextField без буферизации в виде непрерывного потока - PullRequest
2 голосов
/ 09 июля 2011

Что я хочу сделать, это запустить задачу командной строки (CL) (обернутый NSTask) и передать (NSPipe) символьный вывод через метку NSTextField в моем пользовательском интерфейсе в режиме реального времени в виде потока символов.Цель текстового поля - не захватывать вывод каким-либо образом и даже не позволять его читать.Это просто для отображения, частично как украшение пользовательского интерфейса и частично как индикатор прогресса.Я хочу, чтобы пользователь видел поток символов, просто проходящих (быстро), когда задача CL выполняет свою работу.

Я знаю, как обернуть задачу CL в NSTask и получить ее вывод, установив [task setStandardOutput: outputPipe], а затем читать из этого вывода с помощью NSFileHandle.И я думаю, что знаю, как сделать то, что я хочу, «сложным» способом, используя один из методов чтения NSFileHandle и синхронно нарезая выходные данные на куски и отображая эти куски один за другим в текстовом поле.Но я надеюсь, что мог бы быть какой-то легкий способ, которым я не задумывался о том, чтобы перебросить необработанные символы ascii из stdout в текстовое поле в режиме реального времени.

У кого-нибудь есть идея?

РЕДАКТИРОВАТЬ : Вот некоторый рабочий код, основанный на ответе @Peter Hosey.Он делает то, что я хочу, но я не знаю, полностью ли я ухватил концепцию Питера или я делаю здесь что-то нехорошее, поэтому, пожалуйста, не стесняйтесь комментировать.Еще раз спасибо, Питер!

примечания к этому коду:

1) изменение scheduleTimerWithTimeInterval в init с .001 до .005 - интересный визуальный диапазон для эффекта прокрутки текста.

2) метка, которую я использую, была простой текстовой меткой, созданной в моем интерфейсе в конструкторе интерфейсов.для моих целей мне не нужно было делать вторую часть ответа Питера с правильной обоснованной приписанной строкой.я просто устанавливаю выравнивание текстовой метки в конструкторе интерфейсов.

@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 Ответ

3 голосов
/ 10 июля 2011

Асинхронное чтение дескриптора файла , таймер , NSMutableData, который вы ограничиваете фиксированным числом байтов (скажем, 300), сохраняя только последние байты и удаляя старыев байтах и ​​правильном выравнивании в текстовом поле.

Для последней части вам нужно будет сделать изменяемую копию стиль абзаца по умолчанию , установить его выравнивание к выравнивание по правому краю и установите значение строки атрибута текстового поля для атрибутной строки, у которой стиль абзаца имеет значение , один из его атрибутов .

...