Como implementar a fila de trabalho no iOS, onde apenas a solicitação mais recente é tratada?

No meu programa iOS, acontece o seguinte: À medida que o usuário digita, um pedido é disparado para um segmento no qual uma consulta ao banco de dados é iniciada. Quando a consulta ao banco de dados é concluída, uma resposta é disparada no thread principal para que o aplicativo possa exibir os resultados.

Isso funciona muito bem, exceto que, se o usuário digitar muito rápido, pode haver várias solicitações em andamento. Eventualmente, o sistema irá recuperar, mas parece ineficiente.

Existe uma maneira simples de implementá-lo para que, se uma solicitação for iniciada, eu possa detectar que uma pesquisa já está em andamento, e a solicitação deve ser armazenada como "potencialmente a mais nova que excede a que está em trânsito"?

SOLUÇÃO DE AMOSTRA COM COMENTÁRIOS ADICIONADOS ABAIXO

Aqui está o corpo de um controlador de exibição para um pequeno projeto de amostra que ilustra as propriedades da solução. Ao digitar, você pode obter uma saída assim:

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'

Nesse caso, a primeira operação enfileirada é ignorada porque determina que ficou desatualizada enquanto executa a parte longa de seu trabalho. As duas operações seguintes enfileiradas ('de' e 'det') são canceladas antes mesmo de serem autorizadas a serem executadas. A última operação final é a única a terminar realmente todo o seu trabalho.

Se você comentar a linha [self.lookupQueue cancelAllOperations], obterá esse comportamento:

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'

Nesse caso, toda a operação enfileirada executará a parte de comprimento de seu trabalho, mesmo que uma operação mais recente tenha sido enfileirada antes de ela ter sido programada para execução.

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

questionAnswers(3)

yourAnswerToTheQuestion