Como usar um NSProgressIndicator determinado para verificar o andamento do NSTask?- cacau
-
29-10-2019 - |
Pergunta
O que eu tenho é NSTask executando um script de shell premade longo e quero que o NSProgressIndicator verifique o quanto foi feito.Eu tentei muitas coisas, mas simplesmente não consigo fazer funcionar.Sei como usá-lo se a barra de progresso estiver indeterminada, mas quero que carregue conforme a tarefa prossegue.
Aqui está como estou executando o script:
- (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];
}
Preciso colocar uma barra de progresso que verifique o andamento da tarefa enquanto ela acontece e atualize de acordo.
Solução
Aqui está um exemplo de uma NSTask assíncrona executando um script Unix.No script Unix, existem comandos echo
que devolvem o status atual ao erro padrão como este:
echo "working" >&2
Isso é processado pela central de notificações e enviado ao monitor.
Para atualizar uma barra de progresso determinada, basta enviar atualizações de status como "25.0" "26.0" e converter para flutuar e enviar para a barra de progresso.
nota: eu coloquei isso funcionando depois de muitas experiências e usando muitas dicas deste site e outras referências.então espero que seja útil para você.
Aqui estão as declarações:
NSTask *unixTask;
NSPipe *unixStandardOutputPipe;
NSPipe *unixStandardErrorPipe;
NSPipe *unixStandardInputPipe;
NSFileHandle *fhOutput;
NSFileHandle *fhError;
NSData *standardOutputData;
NSData *standardErrorData;
Aqui estão os principais módulos do programa:
- (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
Outras dicas
Você deve ter uma forma de ligar de volta ou interromper o andamento de uma tarefa para saber quanto progresso você fez.Se você está falando sobre um script de shell, você pode quebrar 1 script em vários scripts e, após a conclusão de uma seção do script, atualizar o indicador de progresso.Outros aplicativos fizeram coisas como esta, o iirc Sparkle fez alguma lógica personalizada em seu código de descompactação para descompactar em pedaços para que pudesse atualizar um indicador de progresso.Se você quiser obter o mesmo efeito, terá que fazer algo semelhante.
Obtenha o PID (ID do processo) para o comando que você executou, usando-o em conjunto com o comando PS (Estado do processo), você pode obter o estado do seu processo, use esse valor em seu código para mostrá-lo na barra de progresso
uma maneira simples de fazer seu script (nstask) se comunicar com seu controlador obj c é usando o erro padrão como um canal de comunicação.Não estou dizendo que isso é perfeito, mas funciona muito bem.Você deve configurar sua tarefa como um nstask assíncrono e usar notificações para ler a entrada padrão e o erro padrão separadamente.Posso postar mais tarde se você precisar.
Envolva seu script assim:
echo "now starting" >&2
for i in $(ls /tmp)
do
echo $i 2>&1
done
echo "now ending" >&2
Processe seu erro padrão por meio de um pipe e filehandle e conecte-o a uma saída para atualizações de status textuais ou converta-o em float para exibições de progresso.Ou seja,
echo "25.0" >&2. Can be captured and converted.
Se você precisa capturar o erro padrão real, faça um trap e mescle-o com o padrão padrão como no meu exemplo.Esta não é uma abordagem perfeita, mas eu a uso com frequência e funciona bem.
No meu iPad e não tenho código.Deixe-me saber se você precisar de um exemplo melhor.