Question

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 est terminée.

Quel est le meilleur moyen d'y parvenir?

Je ne peux pas envoyer de notifications à partir de mes NSOperation s, car je ne sais pas laquelle sera la dernière, et [queue operations] n'est peut-être pas encore vide (ou pire - repeuplé) à la réception de la notification.

Était-ce utile?

La solution

Utilisez KVO pour observer la propriété operations de votre file d'attente. Vous pourrez ainsi savoir si votre file d'attente est terminée en recherchant [queue.operations count] == 0.

Quelque part dans le fichier dans lequel vous faites 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, procédez comme suit:

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

Faites ceci 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];
    }
}

(Cela suppose que votre NSOperationQueue se trouve dans une propriété nommée queue)

À un moment donné avant que votre objet ne traite complètement de ses deallocs (ou qu'il ne se soucie plus de l'état de la file d'attente), vous devez vous désinscrire de KVO comme suit:

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


Addendum: iOS 4.0 a une propriété NSOperationQueue.operationCount qui, selon la documentation, est conforme à KVO. Cette réponse fonctionnera toujours sous iOS 4.0, ce qui la rend utile pour la compatibilité avec les versions antérieures.

Autres conseils

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 nombre de " short " des opérations sont ajoutées à une file d'attente, ce comportement peut également apparaître (car les opérations sont démarré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 était terminée, après l'ajout d'un grand nombre d'opérations à une série NSOperationQueue (c'est-à-dire, maxConcurrentOperationCount = 1) et uniquement lorsqu'elles étaient toutes terminées.

Googling J'ai trouvé cette déclaration d'un développeur Apple en réponse à la question & "; est-ce qu'un FIFO série NSoperationQueue? &"; -

  

Si toutes les opérations ont la même priorité (ce qui n'est pas changé après   l'opération est ajoutée à une file d'attente) et toutes les opérations sont toujours -   isReady == OUI au moment où ils sont mis dans la file d'attente des opérations, puis un numéro de série   NSOperationQueue est FIFO.

     

Chris Kane   Cadres Cacao, Pomme

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

Si les opérations sont ajoutées de manière à ne pas détecter le dernier (non déterministe), je pense que vous devez suivre les approches KVO mentionnées ci-dessus, avec une logique de garde supplémentaire pour essayer de: détecter si d'autres opérations peuvent être ajoutées.

:

Pourquoi ne pas ajouter un NSOperation dépendant de tous les autres afin qu'il s'exécute en dernier?

Une alternative consiste à utiliser GCD. Reportez-vous à la cette 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
});

Voici comment je le fais.

Configurez la file d'attente et enregistrez les modifications 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 self) implémente:

- (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 " spinner " est un UIActivityIndicatorView montrant qu'il se passe quelque chose. Évidemment, vous pouvez changer en fonction de ...

Qu'en est-il de l'utilisation de KVO pour observer la propriété operationCount de la file d'attente? Ensuite, vous en entendriez parler lorsque la file d'attente serait vidée et également lorsqu'elle cesserait d'être vide. Traiter avec l'indicateur de progression peut être aussi simple que de simplement faire quelque chose comme:

[indicator setHidden:([queue operationCount]==0)]

Ajouter la dernière opération comme:

NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];

Donc:

- (void)method:(id)object withSelector:(SEL)selector{
     NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];
     [callbackOperation addDependency: ...];
     [operationQueue addOperation:callbackOperation]; 

}

Avec ReactiveObjC , je trouve que cela fonctionne parfaitement:

// skip 1 time here to ignore the very first call which occurs upon initialization of the RAC block
[[RACObserve(self.operationQueue, operationCount) skip:1] subscribeNext:^(NSNumber *operationCount) {
    if ([operationCount integerValue] == 0) {
         // operations are done processing
         NSLog(@"Finished!");
    }
}];

Pour votre information, vous pouvez y parvenir avec dispatch_group de GCD dans swift 3 . Vous pouvez être averti lorsque toutes les tâches sont terminées.

let group = DispatchGroup()

    group.enter()
    run(after: 6) {
      print(" 6 seconds")
      group.leave()
    }

    group.enter()
    run(after: 4) {
      print(" 4 seconds")
      group.leave()
    }

    group.enter()
    run(after: 2) {
      print(" 2 seconds")
      group.leave()
    }

    group.enter()
    run(after: 1) {
      print(" 1 second")
      group.leave()
    }


    group.notify(queue: DispatchQueue.global(qos: .background)) {
      print("All async calls completed")
}

J'utilise une catégorie pour le faire.

NSOperationQueue + Completion.h

//
//  NSOperationQueue+Completion.h
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

typedef void (^NSOperationQueueCompletion) (void);

@interface NSOperationQueue (Completion)

/**
 * Remarks:
 *
 * 1. Invokes completion handler just a single time when previously added operations are finished.
 * 2. Completion handler is called in a main thread.
 */

- (void)setCompletion:(NSOperationQueueCompletion)completion;

@end

NSOperationQueue + Completion.m

//
//  NSOperationQueue+Completion.m
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

#import "NSOperationQueue+Completion.h"

@implementation NSOperationQueue (Completion)

- (void)setCompletion:(NSOperationQueueCompletion)completion
{
    NSOperationQueueCompletion copiedCompletion = [completion copy];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self waitUntilAllOperationsAreFinished];

        dispatch_async(dispatch_get_main_queue(), ^{
            copiedCompletion();
        });
    });
}

@end

Utilisation :

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

[operation2 addDependency:operation1];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation1, operation2] waitUntilFinished:YES];

[queue setCompletion:^{
    // handle operation queue's completion here (launched in main thread!)
}];

Source: https://gist.github.com/artemstepanenko/7620471

Vous pouvez créer un nouveau NSThread ou exécuter un sélecteur en arrière-plan et attendre. Lorsque le NSOperationQueue achèvement, vous pouvez envoyer votre propre notification.

Je pense à quelque chose comme:

- (void)someMethod {
    // Queue everything in your operationQueue (instance variable)
    [self performSelectorInBackground:@selector(waitForQueue)];
    // Continue as usual
}

...

- (void)waitForQueue {
    [operationQueue waitUntilAllOperationsAreFinished];
    [[NSNotificationCenter defaultCenter] postNotification:@"queueFinished"];
}

Si vous utilisez cette opération comme classe de base, vous pouvez passer le bloc whenEmpty {} en bloc. sur la OperationQueue :

let queue = OOperationQueue()
queue.addOperation(op)
queue.addOperation(delayOp)

queue.addExecution { finished in
    delay(0.5) { finished() }
}

queue.whenEmpty = {
    print("all operations finished")
}

Sans KVO

private let queue = OperationQueue()

private func addOperations(_ operations: [Operation], completionHandler: @escaping () -> ()) {
    DispatchQueue.global().async { [unowned self] in
        self.queue.addOperations(operations, waitUntilFinished: true)
        DispatchQueue.main.async(execute: completionHandler)
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top