2

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

这很好用,除了如果用户输入速度非常快,可能会有几个请求在进行中。最终系统会赶上来,但它似乎效率低下。

是否有一种简洁的方法来实现它,以便如果发起请求,我可以检测到查找已经在进行中,并且应该将请求存储为“可能会取代正在进行的请求的最新请求”?

带有以下注释的示例解决方案

这是一个小型示例项目的视图控制器主体,它说明了解决方案的属性。键入时,您可能会得到如下输出:

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

3 回答 3

5

如果您的查询确实需要很长时间,我会想到一种机制,该机制会减慢查询速度,比如说 1s 并取消以前的查询请求(如果有)。因此,如果您使用的是块,它可能是这样的:

@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-05T13:34:14.963 回答
4

对于这种情况,我喜欢 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:] 或类似方法来更新 GUI 或运行必须在主线程上运行的任何其他代码,从 [lookupQueue addOperationWithBlock:] 的块参数内部。

于 2012-11-09T21:11:26.347 回答
1

NSOperationQueue提供了一种方法-cancelAllOperations。所以只要在添加操作时调用它,如果操作已经在运行。剩下的问题是您的NSOperation子类必须定期检查它是否已被取消(并且在这种情况下停止做事)。该检查被放置在您的覆盖中-main

于 2012-11-12T04:48:07.300 回答