Cocoa 如何使用确定的NSPROGRESS指示器检查NSTask的进度热可可
我的任务是运行一个长的预制shell脚本,我希望NSProgressIndicator检查完成了多少。我试过很多方法,但似乎都没能奏效。如果进度条不确定,我知道如何使用它,但我希望随着任务的进行加载它 以下是我运行脚本的方式:Cocoa 如何使用确定的NSPROGRESS指示器检查NSTask的进度热可可,cocoa,nstask,nsprogressindicator,Cocoa,Nstask,Nsprogressindicator,我的任务是运行一个长的预制shell脚本,我希望NSProgressIndicator检查完成了多少。我试过很多方法,但似乎都没能奏效。如果进度条不确定,我知道如何使用它,但我希望随着任务的进行加载它 以下是我运行脚本的方式: - (IBAction)pressButton:(id)sender { NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/bin/sh"]; [task setArgumen
- (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];
}
我需要在其中放置一个进度条,在任务发生时检查该任务的进度,并进行相应的更新。您必须有某种方法回拨或中断任务的进度,以告知您取得了多大的进展。如果您谈论的是一个shell脚本,您可以将一个脚本分解为多个脚本,并在脚本的一部分完成后更新进度指示器。其他应用程序也做过类似的事情,iirc Sparkle在其解压代码中做了一些自定义逻辑来解压成块,以便更新进度指示器。如果你想达到同样的效果,你必须做一些类似的事情。获取你运行的命令的PID(进程ID),将其与PS(进程状态)命令结合使用,你可以获取进程的状态。在代码中使用该值在进度条中显示它。一种简单的脚本生成方法(nstask)通过使用标准错误作为通信通道与obj c控制器进行通信。并不是说这是完美的,但效果相当好。您必须将任务设置为异步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上,没有代码。如果需要更好的示例,请告诉我。这里是一个运行unix脚本的异步NSTask示例。在Unix脚本中,有
echo
命令将当前状态发送回标准错误,如下所示:
echo“working”>&2
这由通知中心处理并发送到显示器
要更新确定的进度条,只需发送状态更新,如“25.0”“26.0”,然后转换为float并发送到进度条
注意:我在做了大量的实验之后,通过使用本网站和其他参考资料中的许多技巧,使这项功能得以实现。所以我希望这对你有帮助
声明如下:
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
ps-获取任何进程状态的命令。如果使用pid(进程id,进程唯一)运行此命令在终端中,它显示进程的状态,该进程的状态为pid。例如:ps-l 185但如果其运行终端不起作用,我将如何将其回调到我的应用程序中这是因为您不能在一个操作中运行多个NSTASK为什么这样说?你可以启动一个NSTask,得到它的结果,启动一个不同的任务等等,我以前写过一些应用程序,可以为各种事情启动多个任务。唯一的区别是我使用了一个封装NSTask的类,但除此之外,您应该也可以这样做,我的意思是,如果我将代码复制并粘贴到同一iAction中,并将变量重命名为task2,更改了绑定和参数,并告诉它waitUntilExit执行第一个任务,它会说它执行了,但什么也没有发生。对不起,我应该更具体地说明什么是不起作用的。听起来你有另一个问题,也许是另一个问题。如果不看你的代码,我不能说太多,但是如果你正确地抽象东西,这样你就不只是粘贴代码,你应该能够轻松地启动多个任务。也许可以创建一个方法来启动任务,并至少自己获得结果。这听起来似乎可行,但我不认为我知道如何做到这一点,所以最好的示例会非常受欢迎。好的,这里是上下文。在异步模式下运行NSTask。拆分标准输出和标准错误。使用通知中心响应这些通道上的数据。使用标准错误创建正在运行的文本状态。基本上,在程序运行时,使用如下echo命令将状态更新发送到标准错误:echo“status report”>&2。它将显示在标准错误管道上,然后您的程序可以在屏幕上显示它。或者不发送状态文本,发送25.0之类的数字,并将其转换为float,然后更新您的确定进度条。谢谢,这已经成功了。我喜欢你用声明的方式,有些人不喜欢,这真的让我很烦,因为我不知道用什么声明。再次感谢您在IBAction的开头,对于正在设置字符串的对象,有哪些声明?是否需要NSMutableArray部分?我不能直接指向一个.sh文件并使用/usr/bin/shI吗?我已经将用户界面操作与任务代码进行了一些混合。IBAction中的命令,只需在按下按钮时清除表单上的标签。然后,任务运行,从std in或std err获取用户的数据,并在任务期间更新这些标签。我还打开进度动画,然后在任务完成后将其关闭。如果需要,可以使用标准NSArray而不是NSMutableArray,但必须提供其中之一。这是NSTask中的方法定义:-(void)setArguments:(NSArray*)argumentsOk这可以工作,但任务栏根本不更新,它始终为空,最后为空。我已经在IB中将它链接到了UnixPressBar,但它什么都没做