90 votes

Obtenir une notification lorsque NSOperationQueue a terminé toutes les tâches.

NSOperationQueue a waitUntilAllOperationsAreFinished mais je ne veux pas l'attendre de manière synchrone. Je veux juste cacher l'indicateur de progression dans l'interface utilisateur lorsque la file d'attente se termine.

Quelle est la meilleure façon d'y parvenir ?

Je ne peux pas envoyer de notifications depuis mon NSOperation parce que je ne sais pas lequel sera le dernier, et [queue operations] peut ne pas être encore vide (ou pire - repeuplé) lorsque la notification est reçue.

165voto

Nick Forge Points 13758

Utilisez KVO pour observer le operations de votre file d'attente, vous pouvez alors savoir si votre file d'attente est terminée en vérifiant la propriété [queue.operations count] == 0 .

Quelque part dans le fichier dans lequel vous effectuez le KVO, déclarez un contexte pour le KVO comme ceci ( plus d'infos ) :

static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged";

Lorsque vous configurez votre file d'attente, faites ceci :

[self.queue addObserver:self forKeyPath:@"operations" options:0 context:&kQueueOperationsChanged];

Ensuite, faites-le dans votre observeValueForKeyPath :

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
                         change:(NSDictionary *)change context:(void *)context
{
    if (object == self.queue && [keyPath isEqualToString:@"operations"] && context == &kQueueOperationsChanged) {
        if ([self.queue.operations count] == 0) {
            // Do something here when your queue has completed
            NSLog(@"queue has completed");
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object 
                               change:change context:context];
    }
}

(Ceci en supposant que votre NSOperationQueue est dans une propriété nommée queue )

À un moment donné, avant que votre objet ne soit complètement désalloué (ou lorsqu'il ne se préoccupe plus de l'état de la file d'attente), vous devrez vous désenregistrer du KVO de la manière suivante :

[self.queue removeObserver:self forKeyPath:@"operations" context:&kQueueOperationsChanged];

Addendum : iOS 4.0 a une NSOperationQueue.operationCount qui, selon la documentation, est conforme à la KVO. Cette réponse fonctionnera toujours dans iOS 4.0 cependant, elle est donc toujours utile pour la rétrocompatibilité.

20voto

software evolved Points 2125

Si vous attendez (ou désirez) quelque chose qui correspond à ce comportement :

t=0 add an operation to the queue.  queueucount increments to 1
t=1 add an operation to the queue.  queueucount increments to 2
t=2 add an operation to the queue.  queueucount increments to 3
t=3 operation completes, queuecount decrements to 2
t=4 operation completes, queuecount decrements to 1
t=5 operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

Vous devez savoir que si un certain nombre d'opérations "courtes" sont ajoutées à une file d'attente, vous pouvez voir ce comportement à la place (parce que les opérations sont lancées dans le cadre de l'ajout à la file d'attente) :

t=0  add an operation to the queue.  queuecount == 1
t=1  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=2  add an operation to the queue.  queuecount == 1
t=3  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=4  add an operation to the queue.  queuecount == 1
t=5  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

Dans mon projet, j'avais besoin de savoir quand la dernière opération s'était terminée, après qu'un grand nombre d'opérations aient été ajoutées à une NSOperationQueue en série (c'est-à-dire, maxConcurrentOperationCount=1) et seulement quand elles s'étaient toutes terminées.

En cherchant sur Google, j'ai trouvé cette déclaration d'un développeur Apple en réponse à la question "un NSoperationQueueue en série est-il FIFO ?" --

Si toutes les opérations ont la même priorité (qui n'est pas modifiée après l'opération est ajoutée à une file d'attente) et que toutes les opérations sont toujours - isReady==YES au moment où elles sont placées dans la file d'attente des opérations, alors une file d'attente NSOperationQueueue en série est FIFO. NSOperationQueueue est FIFO.

Chris Kane Cocoa Frameworks, Apple

Dans mon cas, il est possible de savoir quand la dernière opération a été ajoutée à la file d'attente. Ainsi, après l'ajout de la dernière opération, j'ajoute une autre opération à la file d'attente, de priorité inférieure, qui ne fait rien d'autre qu'envoyer la notification que la file d'attente a été vidée. Compte tenu de la déclaration d'Apple, cela permet de s'assurer qu'une seule notification est envoyée, une fois que toutes les opérations ont été effectuées.

Si les opérations sont ajoutées d'une manière qui ne permet pas de détecter la dernière (c'est-à-dire non déterministe), je pense que vous devez adopter les approches KVO mentionnées ci-dessus, avec une logique de garde supplémentaire ajoutée pour essayer de détecter si d'autres opérations peuvent être ajoutées.

)

17voto

MostlyYes Points 29

Que diriez-vous d'ajouter une NSOpération qui dépend de toutes les autres afin qu'elle soit exécutée en dernier ?

12voto

nhisyam Points 559

Une alternative est d'utiliser le GCD. Se référer à este comme référence.

dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,queue,^{
 NSLog(@"Block 1");
 //run first NSOperation here
});

dispatch_group_async(group,queue,^{
 NSLog(@"Block 2");
 //run second NSOperation here
});

//or from for loop
for (NSOperation *operation in operations)
{
   dispatch_group_async(group,queue,^{
      [operation start];
   });
}

dispatch_group_notify(group,queue,^{
 NSLog(@"Final block");
 //hide progress indicator here
});

5voto

Kris Jenkins Points 2229

C'est comme ça que je fais.

Configurez la file d'attente, et enregistrez les changements dans la propriété des opérations :

myQueue = [[NSOperationQueue alloc] init];
[myQueue addObserver: self forKeyPath: @"operations" options: NSKeyValueObservingOptionNew context: NULL];

...et l'observateur (dans ce cas-ci self ) met en œuvre :

- (void) observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context {

    if (
        object == myQueue
        &&
        [@"operations" isEqual: keyPath]
    ) {

        NSArray *operations = [change objectForKey:NSKeyValueChangeNewKey];

        if ( [self hasActiveOperations: operations] ) {
            [spinner startAnimating];
        } else {
            [spinner stopAnimating];
        }
    }
}

- (BOOL) hasActiveOperations:(NSArray *) operations {
    for ( id operation in operations ) {
        if ( [operation isExecuting] && ! [operation isCancelled] ) {
            return YES;
        }
    }

    return NO;
}

Dans cet exemple, le "spinner" est un UIActivityIndicatorView montrant que quelque chose se passe. Évidemment, vous pouvez changer pour vous adapter...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X