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+キャンセル機能が欲しいという用途には最適だと思います。