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

War es hilfreich?

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

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

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)
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top