Objective-C
AdventCalendar
iOS
iOSDay 8

NSBlockOperationで手軽にキャンセル処理

More than 5 years have passed since last update.

GCD便利ですね。手軽にネストした非同期処理を書けるので、使いまくっています。しかし一直線にコードを書いていると見過ごしがちなのが、キャンセル処理です。時間のかかる処理を途中で止める場合や、別のUIViewControllerに遷移するから処理そのものが必要なくなった場合に、処理を止めたい場合があります。具体的には以下の様なケースがあると思います。

  • 大きなファイルのアップロード中にキャンセルボタンを出したい
  • 画面遷移をしたら、前の画面で進行中の画像ダウンロードはキャンセルしたい

このような場合にGCDライクに使えて、しかもキャンセル処理ができのが組み込みクラスのNSBlockOperationです。早速使い方を見て行きましょう。weakのおかげでややこしいメモリ管理を考えなくて楽です。

-(void)heavyTask {
    if (!_queue)
        _queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *operation = [[NSBlockOperation alloc] init];
    __weak NSBlockOperation *weakOperation = operation; // 循環参照を避ける
    [operation addExecutionBlock:^{
        for (int i = 0; i < 10; i++) {
            if (weakOperation.isCancelled) return;  // これを至る所に挟む
            [NSThread sleepForTimeInterval:1]; // 何か重い処理
        }
        NSLog(@"complete");
    }];

    [_queue addOperation:operation];
}

-(void)cancel {
    [_queue cancelAllOperations];
}

ではネストした処理のキャンセルを考えましょう。全く同じように直感的に書けます。NSOprationQueueがキャンセルの面倒を見てくれるので楽ですね。

-(void)heavyTask {
    if (!_queue)
        _queue = [[NSOperationQueue alloc] init];

    __weak NSOperationQueue *weakQueue = _queue;

    NSBlockOperation *firstOperation = [[NSBlockOperation alloc] init];
    __weak NSBlockOperation *weakFirstOperation = firstOperation; // 循環参照を避ける

    [firstOperation addExecutionBlock:^{
        for (int i = 0; i < 10; i++) {
            if (weakFirstOperation.isCancelled) return;  // これを至る所に挟む
            [NSThread sleepForTimeInterval:1]; // 何か重い処理
        }
        NSLog(@"next");
        NSBlockOperation *secondOperation = [[NSBlockOperation alloc] init];
        __weak NSBlockOperation *weakSecondOperation = secondOperation; // 循環参照を避ける
        [secondOperation addExecutionBlock:^{
            for (int i = 0; i < 10; i++) {
                if (weakSecondOperation.isCancelled) return;
                [NSThread sleepForTimeInterval:1]; // 何か重い処理
            }
            NSLog(@"complete");
        }];
        [weakQueue addOperation:secondOperation];
    }];

    [_queue addOperation:firstOperation];
}

-(void)cancel {
    [_queue cancelAllOperations];
}

ここまでやるとだいぶコードが見づらいですね…。しかしDelegate等で書くと、(設計的には綺麗ですが)後から追いづらくなるので、こっちの方がましではないかと思ってます。

以上NSBlockOperationでキャンセル処理を行う方法の紹介でした。NSOperationQueueは裏側でGCDを使っているので、GCD+キャンセル機能が欲しいという用途には最適だと思います。