如何在仅处理最新请求的iOS中实现工作队列?

在我的iOS程序中,会发生以下情况:在用户键入的情况下,将请求发送到启动数据库查找的线程。 当数据库查找完成后,在主线程上触发响应,以便应用程序可以显示结果。

这样做效果很好,除非用户键入的速度非常快,否则可能有多个请求正在进行。 最终系统将迎头赶上,但似乎效率低下。

有没有一个简单的方法来实现它,以便如果一个请求被启动,我可以检测到一个查询已经在进行中,而该请求应该被存储为“潜在的最新超过一个在飞行中”?

示例解决scheme,添加以下注释

下面是一个小样本项目的视图控制器的主体,它演示了解决scheme的属性。 当你input你可能会得到这样的输出:

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' 

在这种情况下,第一个排队操作被跳过,因为它确定在执行其冗长的部分工作时它已经过时。 接下来的两个排队操作(“de”和“det”)在被允许执行之前被取消。 最后的最终作业是唯一一个真正完成所有工作的人。

如果你注释掉[self.lookupQueue cancelAllOperations]这一行,你会得到这个行为:

 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' 

在这种情况下,所有入队的操作都将执行其工作的长度部分,即使在它已经被调度执行之前,一个新的操作已经被排队。

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

对于这样的情况,我喜欢NSOperationQueue。

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

编辑:只需要注意,你需要使用[[NSOperationQueue mainQueue] addOperationWithBlock:]或类似的方式来更新GUI或者在block参数中运行其他必须在主线程上运行的代码[lookupQueue addOperationWithBlock:]。

如果您的查询真的需要很长时间,我会想到的机制,会减慢查询,让我们说1秒,并取消以前的查询请求,如果有的话。 所以如果你使用块,可能是这样的:

 @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提供了一个方法-cancelAllOperations 。 所以在添加一个操作时,如果一个操作已经在运行,就调用它。 问题的其余部分是你的NSOperation子类必须定期检查它是否被取消(并且在这种情况下停止doin的东西)。 该检查放置在您的覆盖-main