Como usar um NSProgressIndicator determinado para verificar o andamento do NSTask?- cacau

StackOverflow https://stackoverflow.com/questions/8889006

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.

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top