NSTask字符输出到NSTextField未缓冲为连续流 [英] NSTask character output to NSTextField unbuffered as continuous stream

查看:345
本文介绍了NSTask字符输出到NSTextField未缓冲为连续流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要实现的是通过我的UI中的NSTextField标签,以字符流的形式实时地启动命令行(CL)任务(包装NSTask)和管道(NSPipe)字符输出。文本字段的目的不是以任何方式捕获输出,或甚至允许读取它。它只是显示它,部分作为UI装饰,部分作为一种进度指示器。我想让用户看到一个字符流只是流动的(快速)作为CL任务它的工作。



我知道如何包装CL任务在NSTask并通过设置[task setStandardOutput:outputPipe]获取其输出,然后使用NSFileHandle从该输出读取。我想我知道如何做我想要的硬方式使用NSFileHandle读取方法之一,并同步斩切输出成块,并在文本字段逐个显示这些块。但我希望可能有一些轻量级的方式,我没有想到从原始ascii字符从stdout到文本字段的实时爆炸。



任何人都有想法?



EDIT :这里有一些基于@Peter Hosey答案的工作代码。它正在做我想要的,但我不知道我是否彻底地捏造了彼得的概念,或者如果我在这里做什么不好,所以请随意评论。再次感谢Peter!



对此代码的注释:



1)将init中的scheduledTimerWithTimeInterval从.001到.005是一个有趣的视觉范围的文本滚动效果。



2)我使用的标签只是一个简单的文本标签创建在我的UI界面构建器。为我的目的,我不需要做的第二部分彼得的答案与右对齐归因字符串。我只是在界面生成器中设置文本标签的对齐方式。

  @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:零];
_outputData = [[NSMutableData alloc] initWithCapacity:300];
_timer = [NSTimer scheduledTimerWithTimeInterval:.001
target:self
selector:@selector(displayOutput)
userInfo:nil
重复: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;

//从outputData中取出第一个字符
nextByte = [_outputData subdataWithRange:NSMakeRange(0,1)];
nextChar = [[NSString alloc] initWithData:nextByte
encoding:NSASCIIStringEncoding];

//删除数据的第一个字节
[_outputData replaceBytesInRange:NSMakeRange(0,1)withBytes:NULL length:0];

if(![nextChar isEqualToString:@\\\
]){
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];
//将输出写入到后台的outputData
_fileHandle = [outPipe fileHandleForReading];
[_fileHandle readInBackgroundAndNotify];

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

@end


解决方案>

异步读取文件句柄 timer ,一个NSMutableData,你通过只保留一个固定的字节数最后字节和删除旧字节,并在文本字段中右对齐。



对于最后一部分,您需要创建默认段落样式将其对齐设置为右对齐,并设置文本字段的属性字符串值添加到段落样式为其中一个属性


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

解决方案

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.

这篇关于NSTask字符输出到NSTextField未缓冲为连续流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆