如何使用确定的NSProgressIndicator来检查NSTask的进度? - 可可

时间:2012-01-17 02:35:35

标签: cocoa nstask nsprogressindicator

我所拥有的是运行长预制shell脚本的NSTask,我希望NSProgressIndicator检查已完成的工作量。我尝试过很多东西,但似乎无法让它发挥作用。如果进度条不确定,我知道如何使用它,但我希望在任务继续时加载它。

以下是我运行脚本的方法:

- (IBAction)pressButton:(id)sender {
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/bin/sh"];
    [task setArguments:[NSArray arrayWithObjects:[[NSBundle mainBundle] pathForResource:@"script" ofType:@"sh"], nil]];
    [task launch];
}

我需要设置一个进度条来检查该任务发生的进度并相应地更新。

4 个答案:

答案 0 :(得分:7)

以下是运行unix脚本的异步NSTask示例。在Unix脚本中有echo个命令,它们将当前状态发送回标准错误,如下所示:

echo "working" >&2

这由通知中心处理并发送到显示器。

要更新确定的进度条,只需发送状态更新,例如“25.0”“26.0”并转换为浮动并发送到进度条。

注意:经过大量实验并使用了本网站和其他参考资料中的许多提示,我得到了这个工作。所以我希望它对你有所帮助。

以下是声明:

NSTask *unixTask;
NSPipe *unixStandardOutputPipe;
NSPipe *unixStandardErrorPipe;
NSPipe *unixStandardInputPipe;
NSFileHandle *fhOutput;
NSFileHandle *fhError;
NSData *standardOutputData;
NSData *standardErrorData;

以下是主要的程序模块:

    - (IBAction)buttonLaunchProgram:(id)sender {

    [_unixTaskStdOutput setString:@"" ];
    [_unixProgressUpdate setStringValue:@""];
    [_unixProgressBar startAnimation:sender];

    [self runCommand];
}
- (void)runCommand {

    //setup system pipes and filehandles to process output data
    unixStandardOutputPipe = [[NSPipe alloc] init];
    unixStandardErrorPipe =  [[NSPipe alloc] init];

    fhOutput = [unixStandardOutputPipe fileHandleForReading];
    fhError =  [unixStandardErrorPipe fileHandleForReading];

    //setup notification alerts
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    [nc addObserver:self selector:@selector(notifiedForStdOutput:) name:NSFileHandleReadCompletionNotification object:fhOutput];
    [nc addObserver:self selector:@selector(notifiedForStdError:)  name:NSFileHandleReadCompletionNotification object:fhError];
    [nc addObserver:self selector:@selector(notifiedForComplete:)  name:NSTaskDidTerminateNotification object:unixTask];

    NSMutableArray *commandLine = [NSMutableArray new];
    [commandLine addObject:@"-c"];
    [commandLine addObject:@"/usr/bin/kpu -ca"]; //put your script here

    unixTask = [[NSTask alloc] init];
    [unixTask setLaunchPath:@"/bin/bash"];
    [unixTask setArguments:commandLine];
    [unixTask setStandardOutput:unixStandardOutputPipe];
    [unixTask setStandardError:unixStandardErrorPipe];
    [unixTask setStandardInput:[NSPipe pipe]];
    [unixTask launch];

    //note we are calling the file handle not the pipe
    [fhOutput readInBackgroundAndNotify];
    [fhError readInBackgroundAndNotify];
}
-(void) notifiedForStdOutput: (NSNotification *)notified
{

    NSData * data = [[notified userInfo] valueForKey:NSFileHandleNotificationDataItem];
    NSLog(@"standard data ready %ld bytes",data.length);

    if ([data length]){

        NSString * outputString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];  
        NSTextStorage *ts = [_unixTaskStdOutput textStorage];
        [ts replaceCharactersInRange:NSMakeRange([ts length], 0)
                          withString:outputString];
    }

    if (unixTask != nil) {

        [fhOutput readInBackgroundAndNotify];
    }

}
-(void) notifiedForStdError: (NSNotification *)notified
{

    NSData * data = [[notified userInfo] valueForKey:NSFileHandleNotificationDataItem];
    NSLog(@"standard error ready %ld bytes",data.length);

    if ([data length]) {

        NSString * outputString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];  
        [_unixProgressUpdate setStringValue:outputString];
    }

    if (unixTask != nil) {

        [fhError readInBackgroundAndNotify];
    }

}
-(void) notifiedForComplete:(NSNotification *)anotification {

    NSLog(@"task completed or was stopped with exit code %d",[unixTask terminationStatus]);
    unixTask = nil;

    [_unixProgressBar stopAnimation:self];
    [_unixProgressBar viewDidHide];

    if ([unixTask terminationStatus] == 0) {
        [_unixProgressUpdate setStringValue:@"Success"]; 
    }
    else {
        [_unixProgressUpdate setStringValue:@"Terminated with non-zero exit code"];
    }
}
@end

答案 1 :(得分:1)

您必须有一些方法可以回复或中断任务中的任务进度,以告知您取得了多少进展。如果您正在讨论shell脚本,则可以将1个脚本分解为多个脚本,并在完成脚本的一部分后更新进度指示器。其他应用程序已经完成了这样的事情,iirc Sparkle在其解压缩代码中做了一些自定义逻辑,以便在块中解压缩,因此它可以更新进度指示器。如果你想达到同样的效果,你将不得不做类似的事情。

答案 2 :(得分:0)

获取您运行的命令的PID(进程ID),将其与PS(进程状态)命令结合使用,您可以获取进程的状态,使用代码中的该值在进度条中显示< / p>

答案 3 :(得分:0)

让脚本(nstask)与对象控制器通信的一种简单方法是使用标准错误作为通信通道。不是说这是完美的,但效果很好。您必须将任务设置为异步nstask,并使用通知分别从标准输入和标准错误中读取。如果你需要,我可以稍后发布。

像这样包装你的脚本:

echo "now starting" >&2
for i in $(ls /tmp)
do 
echo $i 2>&1
done
echo "now ending" >&2

通过管道和文件句柄处理标准错误并将其连接到插座以进行文本状态更新,或将其转换为浮动以显示进度。我是

echo "25.0" >&2. Can be captured and converted.

如果您需要捕获真正的std错误,那么将其捕获并将其合并到std中,就像我的示例一样。这不是一个完美的方法,但我经常使用它,它运作良好。

在我的iPad上没有代码。如果您需要更好的例子,请告诉我。