如何在后台运行NSTask并在运行时以模态NSWindow显示结果

时间:2014-07-25 17:52:27

标签: macos cocoa nswindow nstask

我想用NSTask执行命令,并能够在模态窗口中看到进度。例如,如果我执行' ls -R /'我希望看到这些块出现在NSTextView中。

我想出了以下内容,除了更新部分外,一切正常。任务执行(旋转海滩),完成后我会看到结果显示在textview中。

@interface ICA_RunWindowController ()

@property (strong) IBOutlet NSTextView* textResult;
@property (strong) IBOutlet NSButton* buttonAbort;
@property (strong) IBOutlet NSButton* buttonOK;

- (IBAction) doOK:(id) sender;
- (IBAction) doAbort:(id) sender;

@end

@implementation ICA_RunWindowController {
  NSTask * executionTask;
  id taskObserver;
  NSFileHandle * errorFile;
  id errorObserver;
  NSFileHandle * outputFile;
  id outputObserver;
  }

@synthesize textResult,buttonAbort,buttonOK;  

- (IBAction)doOK:(id)sender {
  [[self window] close];
  [NSApp stopModal];
  }

- (IBAction)doAbort:(id)sender {
  [executionTask terminate];
  }

- (void) taskCompleted {
NSLog(@"Task completed");
  [[NSNotificationCenter defaultCenter] removeObserver:taskObserver];
  [[NSNotificationCenter defaultCenter] removeObserver:errorObserver];
  [[NSNotificationCenter defaultCenter] removeObserver:outputObserver];
  [self outputAvailable];
  [self errorAvailable];
  executionTask = nil;
  [buttonAbort setEnabled:NO];
  [buttonOK setEnabled:YES];
  }


- (void) appendText:(NSString *) text inColor:(NSColor *) textColor {
  NSDictionary * makeUp = [NSDictionary dictionaryWithObject:textColor forKey:NSForegroundColorAttributeName];
  NSAttributedString * extraText = [[NSAttributedString alloc] initWithString:text attributes:makeUp];
  [textResult setEditable:YES];
  [textResult setSelectedRange:NSMakeRange([[textResult textStorage] length], 0)];
  [textResult insertText:extraText];
  [textResult setEditable:NO];
  [textResult display];
  }

- (void) outputAvailable {
  NSData * someData = [outputFile readDataToEndOfFile];
  if ([someData length] > 0) {
NSLog(@"output Available");
    NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
    [self appendText:someText inColor:[NSColor blackColor]];
    }
  }

- (void) errorAvailable {
  NSData * someData = [errorFile readDataToEndOfFile];
  if ([someData length] > 0) {
NSLog(@"Error Available");
    NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
    [self appendText:someText inColor:[NSColor redColor]];
    }
  }

- (void) runCommand:(NSString *) command {
// make sure all views are initialized
  [self showWindow:[self window]];
// some convience vars
  NSArray * runLoopModes = @[NSDefaultRunLoopMode, NSRunLoopCommonModes];
  NSNotificationCenter * defCenter = [NSNotificationCenter defaultCenter];
// create an task
  executionTask = [[NSTask alloc] init];
// fill the parameters for the task
  [executionTask setLaunchPath:@"/bin/sh"];
  [executionTask setArguments:@[@"-c",command]];
// create an observer for Termination
  taskObserver = [defCenter addObserverForName:NSTaskDidTerminateNotification
                                        object:executionTask 
                                         queue:[NSOperationQueue mainQueue]
                                    usingBlock:^(NSNotification *note) 
    {
      [self taskCompleted];        
      }
    ];

// Create a pipe and a filehandle for reading errors
  NSPipe * error = [[NSPipe alloc] init];
  [executionTask setStandardError:error];
  errorFile = [error fileHandleForReading];
  errorObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
                                         object:errorFile 
                                          queue:[NSOperationQueue mainQueue]
                                     usingBlock:^(NSNotification *note) 
    {
      [self errorAvailable];        
      [errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
      }
    ];
  [errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];

// Create a pipe and a filehandle for reading output
  NSPipe * output = [[NSPipe alloc] init];
  [executionTask setStandardOutput:output];
  outputFile = [output fileHandleForReading];
  outputObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
                                          object:outputFile 
                                           queue:[NSOperationQueue mainQueue]
                                      usingBlock:^(NSNotification *note) 
    {
      [self outputAvailable];        
      [outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
      }
    ];
  [outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];

// start task
  [executionTask launch];

// show our window as modal
  [NSApp runModalForWindow:[self window]];
  }

我的问题:在任务运行时是否可以更新输出?如果是的话,我怎么能实现呢?

1 个答案:

答案 0 :(得分:1)

模态窗口在NSModalPanelRunLoopMode中运行运行循环,因此您需要将其添加到runLoopModes

你不应该得到旋转的沙滩球。原因是您在-readDataToEndOfFile-outputAvailable方法中调用了-errorAvailable。鉴于您正在使用-waitForDataInBackgroundAndNotifyForModes:,您可以使用-availableData方法获取可用数据而不会阻止。

或者,您可以使用-readInBackgroundAndNotifyForModes:,监控NSFileHandleReadCompletionNotification通知,并在处理程序中使用[[note userInfo] objectForKey:NSFileHandleNotificationDataItem]从通知对象中获取数据。换句话说,让NSFileHandle为您完成阅读数据的工作。

但是,无论哪种方式,一旦获得文件结束指示符(空NSData),就不应重新发出...InBackgroundAndNotifyForModes:调用。如果你这样做,你就会忙着旋转,因为它会一遍又一遍地为你提供相同的文件结束指示。

无需手动-display您的文字视图。一旦你修复了导致旋转色轮光标的阻塞调用,这也将允许正常的窗口更新自动发生。