在我的iOS程序中,会发生以下情况:当用户输入时,一个请求被触发到一个启动数据库查找的线程。当DB查找完成时,会在主线程上触发一个响应,以便应用程序可以显示结果。
这很有效,除了如果用户输入非常快,可能会有几个请求在运行中。最终,系统会迎头赶上,但效率似乎很低。
有没有一种巧妙的方法来实现它,以便如果一个请求被发起,我可以检测到一个查找已经在进行中,并且该请求应该被存储为“潜在的最新的,它取代了正在运行的那个”?
下面添加了注释的示例解决方案
下面是一个小型示例项目的视图控制器的主体,它演示了解决方案的属性。在键入时,您可能会得到如下输出:
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,
// 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);
}
}];
}发布于 2012-11-10 05:11:26
我喜欢在这种情况下使用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,
// i.e. this one. Operations are removed when they are completed or cancelled.
}
}];
}编辑:只需注意,您需要使用[NSOperationQueue mainQueue addOperationWithBlock:]或类似的工具来更新图形用户界面或运行必须在主线程上运行的任何其他代码,从lookupQueue addOperationWithBlock:的块参数内部。
发布于 2012-11-05 21:34:15
如果你的查询真的花了很长时间,我会考虑一种机制,让查询速度减慢,比如说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
});
}发布于 2012-11-12 12:48:07
NSOperationQueue提供了一个方法-cancelAllOperations。所以,如果一个操作已经在运行,那么就在添加操作时调用它。剩下的问题是,您的NSOperation子类必须定期检查它是否已被取消(并在该场景中停止执行操作)。该检查放在您的-main覆盖中。
https://stackoverflow.com/questions/13232655
复制相似问题