Erhalten Sie Benachrichtigung, wenn NSOperationQueue alle Aufgaben beendet
-
20-08-2019 - |
Frage
NSOperationQueue
hat waitUntilAllOperationsAreFinished
, aber ich will nicht synchron warten, bis es. Ich möchte nur Fortschrittsanzeige in UI verstecken, wenn Warteschlange beendet ist.
Was ist der beste Weg, dies zu erreichen?
Ich kann keine Benachrichtigungen von meinem NSOperation
s schreiben, da ich nicht weiß, was ein letzte sein wird, und [queue operations]
vielleicht nicht leer sein noch (oder noch schlimmer - repopulated). Wenn eine Benachrichtigung empfangen wird
Lösung
KVO Verwenden Sie das operations
Eigentum Ihrer Warteschlange zu beobachten, dann kann man sagen, ob die Warteschlange durch Überprüfung für [queue.operations count] == 0
abgeschlossen hat.
Irgendwo in der Datei, die Sie in der KVO tun, einen Kontext für KVO wie folgt erklären ( weitere Informationen ):
static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged";
Wenn Sie Setup-Warteschlange, dies zu tun:
[self.queue addObserver:self forKeyPath:@"operations" options:0 context:&kQueueOperationsChanged];
Dann tun Sie dies in Ihrem 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 wird vorausgesetzt, dass Ihr NSOperationQueue
in einer Eigenschaft namens queue
ist)
An einem gewissen Punkt, bevor Ihr Objekt vollständig deallocs (oder, wenn es aufhört über den Warteschlangenstatus Pflege), werden Sie müssen von KVO wie das Aufheben der Registrierung:
[self.queue removeObserver:self forKeyPath:@"operations" context:&kQueueOperationsChanged];
Nachtrag: iOS 4.0 hat eine NSOperationQueue.operationCount
Eigenschaft, die nach der docs ist KVO-konform. Diese Antwort wird nach wie vor in iOS 4.0 jedoch arbeiten, so ist es immer noch nützlich für die Abwärtskompatibilität.
Andere Tipps
Wenn Sie erwarten (oder Wunsch) etwas, das dieses 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 sich bewusst sein, dass, wenn eine Reihe von „short“ Operationen zu einer Warteschlange hinzugefügt werden Sie dieses Verhalten sehen können stattdessen (weil Operationen als Teil wird hinzugefügt, um die 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 ich musste wissen, wann die letzte Operation beendet, nachdem eine große Anzahl von Operationen an einen seriellen NSOperationQueue hinzugefügt worden war (dh maxConcurrentOperationCount = 1) und nur dann, wenn sie alle abgeschlossen hatten.
Googeln ich diese Aussage von einem Apple-Entwickler als Antwort auf die Frage gefunden „ist ein serielles NSOperationQueue FIFO?“ -
Wenn alle Operationen haben die gleiche Priorität (die nicht geändert wird, nachdem der Betrieb wird in eine Warteschlange hinzugefügt) und alle Operationen sind immer - isReady == JA durch die Zeit, die sie in den Betrieb Warteschlange gestellt bekommen, dann eine serielle NSOperationQueue ist FIFO.
Chris Kane Cocoa Frameworks Apple
In meinem Fall ist es möglich, zu wissen, wann die letzte Operation der Warteschlange hinzugefügt wurde. So nach der letzten Operation hinzugefügt wird, füge ich eine weitere Operation in die Warteschlange, von geringerer Priorität, die nichts tut, aber die Meldung senden, die die Warteschlange geleert worden war. Apples Aussage gegeben, damit sichergestellt, dass nur eine einzige Mitteilung nur gesendet wird, nachdem alle Vorgänge abgeschlossen sind.
Wenn Operationen in eine Weise hinzugefügt werden, die nicht die letzte Erfassung erlaubt, (dh nicht-deterministisch) dann denke ich, Sie mit der KVO gehen Ansätze oben erwähnt, mit zusätzlicher guard Logik hinzugefügt, um zu versuchen, festzustellen, ob weitere Operationen hinzugefügt werden.
:)
Wie wäre es ein NSOperation hinzufügen, die auf allen anderen abhängig ist, so wird es laufen dauern?
Eine Alternative ist GCD zu verwenden. 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
});
Dies ist, wie ich es tun.
die Warteschlange einrichten und für Änderungen im Betrieb Eigenschaft registrieren:
myQueue = [[NSOperationQueue alloc] init];
[myQueue addObserver: self forKeyPath: @"operations" options: NSKeyValueObservingOptionNew context: NULL];
... und die 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 „Spinner“ ist ein UIActivityIndicatorView
zeigt, dass etwas passiert. Natürlich können Sie anpassen ändern ...
Was ist KVO mit der operationCount
Eigenschaft der Warteschlange zu beobachten? Dann würde man darüber hören, wenn die Warteschlange leer ging, und auch wenn er gestoppt leer zu sein. mit dem Fortschrittsanzeige Umgang könnte so einfach sein wie nur tun, so etwas wie:
[indicator setHidden:([queue operationCount]==0)]
In der letzten Operation wie:
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];
}
Mit ReactiveObjC Ich finde das funktioniert 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!");
}
}];
Zu Ihrer Information, Sie können dies erreichen mit GCD dispatch_group in swift 3 . Sie können benachrichtigt, wenn alle Aufgaben fertig 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")
}
Ich verwende eine Kategorie, dies zu tun.
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
Verwendung :
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!)
}];
Sie können einen neuen NSThread
, erstellen oder einen Selektor im Hintergrund ausgeführt werden, und dort warten. Wenn der NSOperationQueue
abgeschlossen wird, können Sie eine Benachrichtigung über Ihre eigenen 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"];
}
Wenn Sie diese Betrieb als Basisklasse , könnten Sie whenEmpty {}
Block zum Pass OperationQueue :
let queue = OOperationQueue()
queue.addOperation(op)
queue.addOperation(delayOp)
queue.addExecution { finished in
delay(0.5) { finished() }
}
queue.whenEmpty = {
print("all operations finished")
}
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)
}
}