Wie implementiere ich eine Arbeitswarteschlange in iOS, in der nur die neuesten Anforderungen verarbeitet werden?

In meinem iOS-Programm geschieht Folgendes: Während der Benutzereingabe wird eine Anforderung an einen Thread gesendet, in dem eine Datenbanksuche initiiert wird. Wenn die Datenbanksuche abgeschlossen ist, wird im Hauptthread eine Antwort ausgelöst, damit die App die Ergebnisse anzeigen kann.

Dies funktioniert hervorragend, außer dass, wenn der Benutzer sehr schnell tippt, möglicherweise mehrere Anforderungen im Flug vorliegen. Irgendwann wird das System aufholen, aber es scheint ineffizient.

Gibt es eine gute Möglichkeit, es zu implementieren, sodass ich beim Initiieren einer Anforderung feststellen kann, dass bereits eine Suche ausgeführt wird, und die Anforderung stattdessen als "potenziell neueste Anforderung, die die aktuelle übertrifft" gespeichert werden sollte?

PROBENLÖSUNG MIT NACHFOLGENDEN KOMMENTAREN

Hier ist der Hauptteil eines Ansichts-Controllers für ein kleines Beispielprojekt, das die Eigenschaften der Lösung veranschaulicht. Bei der Eingabe erhalten Sie möglicherweise die folgende Ausgabe:

2012-11-11 11:50:20.595 TestNsOperation[1168:c07] Queueing with 'd'
2012-11-11 11:50:20.899 TestNsOperation[1168:c07] Queueing with 'de'
2012-11-11 11:50:21.147 TestNsOperation[1168:c07] Queueing with 'det'
2012-11-11 11:50:21.371 TestNsOperation[1168:c07] Queueing with 'dett'
2012-11-11 11:50:21.599 TestNsOperation[1168:1b03] Skipped as out of date with 'd'
2012-11-11 11:50:22.605 TestNsOperation[1168:c07] Up to date with 'dett'

In diesem Fall wird der erste Vorgang in der Warteschlange übersprungen, da festgestellt wird, dass er veraltet ist, während der lange Teil seiner Arbeit ausgeführt wird. Die beiden folgenden Operationen in der Warteschlange ('de' und 'det') werden abgebrochen, bevor sie überhaupt ausgeführt werden dürfen. Die letzte letzte Operation ist die einzige, die tatsächlich alle ihre Arbeiten beendet.

Wenn Sie die Zeile [self.lookupQueue cancelAllOperations] auskommentieren, erhalten Sie stattdessen das folgende Verhalten:

2012-11-11 11:55:56.454 TestNsOperation[1221:c07] Queueing with 'd'
2012-11-11 11:55:56.517 TestNsOperation[1221:c07] Queueing with 'de'
2012-11-11 11:55:56.668 TestNsOperation[1221:c07] Queueing with 'det'
2012-11-11 11:55:56.818 TestNsOperation[1221:c07] Queueing with 'dett'
2012-11-11 11:55:56.868 TestNsOperation[1221:c07] Queueing with 'dette'
2012-11-11 11:55:57.458 TestNsOperation[1221:1c03] Skipped as out of date with 'd'
2012-11-11 11:55:58.461 TestNsOperation[1221:4303] Skipped as out of date with 'de'
2012-11-11 11:55:59.464 TestNsOperation[1221:1c03] Skipped as out of date with 'det'
2012-11-11 11:56:00.467 TestNsOperation[1221:4303] Skipped as out of date with 'dett'
2012-11-11 11:56:01.470 TestNsOperation[1221:c07] Up to date with 'dette'

In diesem Fall führen alle in die Warteschlange gestellten Vorgänge den gesamten Teil ihrer Arbeit aus, auch wenn ein neuerer Vorgang nach ihm in die Warteschlange gestellt wurde, bevor er überhaupt zur Ausführung eingeplant wurde.

@interface SGPTViewController ()

@property (nonatomic, strong) NSString* oldText;
@property (strong) NSOperationQueue *lookupQueue;

@end

@implementation SGPTViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.oldText = self.source.text;
    self.lookupQueue = [[NSOperationQueue alloc] init];
    self.lookupQueue.maxConcurrentOperationCount = 1;
}

- (void)textViewDidChange:(UITextView *)textView
{
    // avoid having a strong reference to self in the operation queue
    SGPTViewController * __weak blockSelf = self;

    // you can cancel existing operations here if you want
    [self.lookupQueue cancelAllOperations];

    NSString *outsideTextAsItWasWhenStarted = [NSString stringWithString:self.source.text];
    NSLog(@"Queueing with '%@'", outsideTextAsItWasWhenStarted);
    [self.lookupQueue addOperationWithBlock:^{
        // do stuff
        NSString *textAsItWasWhenStarted = [NSString stringWithString:outsideTextAsItWasWhenStarted];
        [NSThread sleepForTimeInterval:1.0];
        if (blockSelf.lookupQueue.operationCount == 1) {
            // do more stuff if there is only one operation on the queue,
            // i.e. this one. Operations are removed when they are completed or cancelled.
            // I should be canceled or up to date at this stage
            dispatch_sync(dispatch_get_main_queue(), ^{
                if (![textAsItWasWhenStarted isEqualToString:self.source.text]) {
                    NSLog(@"NOT up to date with '%@'", textAsItWasWhenStarted);
                } else {
                    NSLog(@"Up to date with '%@'", textAsItWasWhenStarted);
                }
            });
        } else {
            NSLog(@"Skipped as out of date with '%@'", textAsItWasWhenStarted);
        }
    }];
}

Antworten auf die Frage(3)

Ihre Antwort auf die Frage