it-swarm.com.de

Lassen Sie sich benachrichtigen, wenn NSOperationQueue alle Aufgaben abgeschlossen hat

NSOperationQueue hat waitUntilAllOperationsAreFinished, aber ich möchte nicht synchron darauf warten. Ich möchte nur den Fortschrittsindikator in der Benutzeroberfläche ausblenden, wenn die Warteschlange abgeschlossen ist.

Was ist der beste Weg, um dies zu erreichen?

Ich kann keine Benachrichtigungen von meinen NSOperations senden, da ich nicht weiß, welcher der letzte ist, und [queue operations] möglicherweise noch nicht leer ist (oder schlechter gefüllt wird), wenn eine Benachrichtigung eingeht.

85
Kornel

Verwenden Sie KVO, um die operations -Eigenschaft Ihrer Warteschlange zu beobachten. Sie können dann feststellen, ob Ihre Warteschlange abgeschlossen ist, indem Sie auf [queue.operations count] == 0 überprüfen.

Erklären Sie irgendwo in der Datei, in der Sie den KVO ausführen, einen Kontext für KVO wie folgt ( weitere Informationen ):

static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged";

Wenn Sie Ihre Warteschlange einrichten, führen Sie folgende Schritte aus:

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

Dann mach das in deiner 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];
    }
}

(Dies setzt voraus, dass sich Ihre NSOperationQueue in einer Eigenschaft mit dem Namen queue befindet.)

Irgendwann bevor Ihr Objekt vollständig freigegeben wird (oder wenn es sich nicht mehr um den Warteschlangenstatus kümmert), müssen Sie die Registrierung von KVO wie folgt aufheben:

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


Nachtrag: iOS 4.0 verfügt über eine NSOperationQueue.operationCount-Eigenschaft, die laut den Dokumenten KVO-kompatibel ist. Diese Antwort wird jedoch unter iOS 4.0 weiterhin funktionieren. Daher ist sie auch für die Abwärtskompatibilität nützlich.

161
Nick Forge

Wenn Sie etwas erwarten (oder wünschen), das diesem Verhalten entspricht:

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>

Sie sollten wissen, dass, wenn mehrere "kurze" Vorgänge zu einer Warteschlange hinzugefügt werden, dieses Verhalten möglicherweise stattdessen angezeigt wird (da Vorgänge als Teil des Hinzufügens zu der Warteschlange gestartet werden):

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>

In meinem Projekt musste ich wissen, wann die letzte Operation abgeschlossen war, nachdem eine große Anzahl von Operationen zu einer seriellen NSOperationQueue hinzugefügt wurde (dh maxConcurrentOperationCount = 1), und nur dann, wenn sie alle abgeschlossen waren.

Googeln Ich fand diese Aussage von einem Apple-Entwickler als Antwort auf die Frage "Ist ein serieller NSoperationQueue-FIFO?" - 

Wenn alle Vorgänge dieselbe Priorität haben (die nach .__ nicht geändert wird, wird der Vorgang einer Warteschlange hinzugefügt), und alle Vorgänge sind immer - isReady == YES zu dem Zeitpunkt, zu dem sie in die Operationswarteschlange gestellt werden, dann eine serielle NSOperationQueue ist FIFO. 

Chris Kane Cocoa Frameworks, Apple

In meinem Fall ist es möglich zu wissen, wann die letzte Operation zur Warteschlange hinzugefügt wurde. Nachdem die letzte Operation hinzugefügt wurde, füge ich der Warteschlange eine weitere Operation mit niedrigerer Priorität hinzu, die nichts weiter tut, als die Benachrichtigung zu senden, dass die Warteschlange geleert wurde. Dies stellt nach Apples Aussage sicher, dass nur eine einzige Benachrichtigung gesendet wird, nachdem alle Vorgänge abgeschlossen sind.

Wenn Operationen auf eine Weise hinzugefügt werden, die das Erkennen des letzten nicht zulässt (dh nicht deterministisch), müssen Sie sich an die oben genannten KVO-Ansätze halten, wobei zusätzliche Überwachungslogik hinzugefügt wird, um zu ermitteln, ob weitere Operationen möglich sind Operationen können hinzugefügt werden.

:)

20

Wie wäre es, eine NSOperation hinzuzufügen, die von allen anderen abhängig ist, damit sie zuletzt ausgeführt wird?

16
MostlyYes

Eine Alternative ist die Verwendung von GCD. Siehe this als Referenz. 

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
});
12
nhisyam

So mache ich es.

Richten Sie die Warteschlange ein, und registrieren Sie sich für Änderungen in der Eigenschaft operation:

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

... und der Beobachter (in diesem Fall self) implementiert:

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

In diesem Beispiel ist "Spinner" eine UIActivityIndicatorView, die zeigt, dass etwas passiert. Natürlich können Sie es ändern ...

5
Kris Jenkins

Fügen Sie den letzten Vorgang wie folgt hinzu:

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

So:

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

}
2
pvllnspk

Wie sieht es mit KVO aus, um die operationCount-Eigenschaft der Warteschlange zu beobachten? Dann haben Sie davon gehört, als die Warteschlange leer wurde und auch, wenn sie nicht mehr leer war. Der Umgang mit der Fortschrittsanzeige kann so einfach sein wie das Folgende:

[indicator setHidden:([queue operationCount]==0)]
2
Sixten Otto

Mit ReactiveObjC finde ich das gut:

// 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!");
    }
}];
2
Stunner

Ich benutze dazu eine Kategorie.

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

Verwendungszweck:

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!)
}];

Quelle: https://Gist.github.com/artemstepanenko/7620471

1
brandonscript

Zu Ihrer Information, Sie können dies mit GCD dispatch_group in Swift 3 erreichen. Sie können benachrichtigt werden, wenn alle Aufgaben abgeschlossen sind.

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")
}

Wenn Sie diese Operation als Basisklasse verwenden, können Sie den whenEmpty {}-Block an die OperationQueue übergeben:

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

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

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

Ohne 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)
    }
}
0
kasyanov-ms

Sie können eine neue NSThread erstellen oder einen Selektor im Hintergrund ausführen und dort warten. Wenn die NSOperationQueue abgeschlossen ist, können Sie eine eigene Benachrichtigung senden.

Ich denke an so etwas wie:

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

...

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