Come utilizzare un determinato NSProgressIndicator per controllare lo stato di avanzamento di NSTask?- Cacao

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

Domanda

Quello che ho è che NSTask esegue un lungo script di shell premade e voglio che NSProgressIndicator controlli quanto è stato fatto.Ho provato molte cose ma non riesco a farlo funzionare.So come usarlo se la barra di avanzamento è indeterminata, ma voglio che venga caricata man mano che l'attività va avanti.

Ecco come eseguo lo 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];
}

Devo inserire una barra di avanzamento che controlli lo stato di avanzamento di tale attività mentre viene eseguita e aggiornarla di conseguenza.

È stato utile?

Soluzione

Di seguito è riportato un esempio di NSTask asincrono che esegue uno script unix.All'interno dello script Unix ci sono comandi echo che rimandano lo stato corrente allo standard error come questo:

echo "working" >&2

Viene elaborato dal centro notifiche e inviato al display.

Per aggiornare una determinata barra di avanzamento basta inviare aggiornamenti di stato come "25.0" "26.0" e convertirli in float e inviarli alla barra di avanzamento.

nota: ho funzionato dopo un sacco di esperimenti e utilizzando molti suggerimenti da questo sito e altri riferimenti.quindi spero che ti sia utile.

Ecco le dichiarazioni:

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

Ecco i principali moduli del programma:

    - (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

Altri suggerimenti

Devi avere un modo per richiamare o interrompere lo stato di avanzamento di un compito per dire quanti progressi hai fatto.Se stai parlando di uno script di shell, potresti suddividere 1 script in più script e al termine di una sezione dello script aggiornare l'indicatore di avanzamento.Altre app hanno fatto cose come questa, iirc Sparkle ha fatto una logica personalizzata nel suo codice di decompressione per decomprimere in blocchi in modo da poter aggiornare un indicatore di avanzamento.Se vuoi ottenere lo stesso effetto, dovrai fare qualcosa di simile.

Ottieni il PID (ID processo) per il comando che hai eseguito, usandolo insieme al comando PS (Stato processo) puoi ottenere lo stato del tuo processo usa quel valore nel tuo codice per mostrarlo nella barra di avanzamento

un modo semplice per far comunicare lo script (nstask) con il controller obj c è utilizzare l'errore standard come canale di comunicazione.Non dico che sia perfetto ma funziona abbastanza bene.È necessario impostare l'attività come nstask asincrono e utilizzare le notifiche per leggere separatamente dallo standard input e dallo standard error.Posso pubblicarlo più tardi se ne hai bisogno.

Avvolgi il tuo script in questo modo:

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

Elabora il tuo errore standard attraverso un pipe e un filehandle e collegalo a una presa per aggiornamenti di stato testuali o convertilo in un float per visualizzare i progressi.Cioè

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

Se è necessario catturare l'errore std reale, intercettalo e uniscilo allo std come il mio esempio.Questo non è un approccio perfetto ma lo uso frequentemente e funziona bene.

Sul mio iPad e non ho il codice.Fammi sapere se hai bisogno di un esempio migliore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top