2

私の 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'

この場合、最初にエンキューされた操作はスキップされます。これは、作業の長い部分を実行している間に古くなったと判断されるためです。次の 2 つのキューに入れられた操作 ('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

クエリに本当に時間がかかる場合は、たとえば 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-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 に答える