Comment implémenter la queue de travail dans iOS où seule la dernière requête est traitée?

Dans mon programme iOS, les events suivants se produisent: Lorsque l'user tape, une requête est renvoyée vers un thread où une search de database est lancée. Lorsque la search de database est terminée, une réponse est déclenchée sur le thread principal afin que l'application puisse afficher les résultats.

Cela fonctionne très bien, sauf que si l'user tape très vite, il peut y avoir plusieurs requests en vol. Finalement, le système rattrapera, mais il semble inefficace.

Existe-t-il une manière simple de l'implémenter de sorte que si une request est lancée, je peux détecter qu'une search est déjà en cours, et la requête devrait plutôt être stockée comme "potentiellement plus récente qui dépasse celle en vol"?

EXEMPLE DE SOLUTION AVEC DES COMMENTAIRES AJOUTÉS CI-DESSOUS

Voici le corps d'un controller de vue pour un petit exemple de projet qui illustre les propriétés de la solution. Lorsque vous tapez, vous pourriez get une sortie comme ceci:

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' 

Dans ce cas, la première opération mise en queue est ignorée car elle détermine qu'elle est devenue obsolète lors de l'exécution de la longue partie de son travail. Les deux opérations suivantes en queue ('de' et 'det') sont annulées avant même d'avoir été autorisées à s'exécuter. L'opération finale finale est la seule à terminer réellement tout son travail.

Si vous mettez en commentaire la ligne [self.lookupQueue cancelAllOperations], vous obtenez ce comportement à la place:

 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' 

Dans ce cas, toutes les opérations mises en queue effectueront la partie longueur de leur travail, même si une opération plus récente a été mise en queue après celle-ci avant même d'avoir été programmée pour l'exécution.

 @interface SGPTViewController () @property (nonatomic, strong) NSSsortingng* 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]; NSSsortingng *outsideTextAsItWasWhenStarted = [NSSsortingng ssortingngWithSsortingng:self.source.text]; NSLog(@"Queueing with '%@'", outsideTextAsItWasWhenStarted); [self.lookupQueue addOperationWithBlock:^{ // do stuff NSSsortingng *textAsItWasWhenStarted = [NSSsortingng ssortingngWithSsortingng:outsideTextAsItWasWhenStarted]; [NSThread sleepForTimeInterval:1.0]; if (blockSelf.lookupQueue.operationCount == 1) { // do more stuff if there is only one operation on the queue, // ie 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 isEqualToSsortingng: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); } }]; } 

    J'aime NSOperationQueue pour des situations comme celle-ci.

     @interface ClassName () ... // atomic since it does not specify nonatomic @property (strong) NSOperationQueue *lookupQueue; ... @end - (id)init { ... lookupQueue = [[NSOperationQueue alloc] init]; lookupQueue.maxConcurrentOperationCount = 1; ... } - (void)textFieldDidChange { // avoid having a strong reference to self in the operation queue ClassName * __weak blockSelf = self; // you can cancel existing operations here if you want // [lookupQueue cancelAllOperations]; [lookupQueue addOperationWithBlock:^{ // do stuff ... if (blockSelf.lookupQueue.operationCount == 1) { // do more stuff if there is only one operation on the queue, // ie this one. Operations are removed when they are completed or cancelled. } }]; } 

    Edit: Juste une note, vous devez utiliser [[NSOperationQueue mainQueue] addOperationWithBlock:] ou similaire pour mettre à jour l'interface graphique ou exécuter tout autre code qui doit aller sur le thread principal, depuis l'argument block vers [lookupQueue addOperationWithBlock:].

    Si votre requête prend vraiment beaucoup de time je penserais à un mécanisme qui ralentirait la requête en disons 1s et annulerait les requests de requêtes précédentes le cas échéant. Donc, si vous utilisiez des blocs, ça aurait pu être quelque chose comme ça:

     @interface YourViewController @property(assign) NSInteger currentTaskId; // atomic ... @implementation YourViewController @synthesize currentTaskId; // your target method - (void)textFieldDidChange { self.currentTaskId = self.currentTaskId + 1; NSInteger taskId = self.currentTaskId; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), queue, ^{ if (taskId == self.currentTaskId) // this is still current task { // your query if (taskId == self.currentTaskId) // sill current after query? update visual elements { // your main thread updates } } // else - there is newer task so skip this old query }); } 

    NSOperationQueue fournit une méthode -cancelAllOperations . Appelez simplement cela lors de l'ajout d'une opération si une opération est déjà en cours. Le rest du problème est que votre sous-class NSOperation doit vérifier périodiquement si elle a été annulée (et arrêter doin 'stuff, dans ce scénario). Cette vérification est placée dans votre override de -main .