2010年9月5日日曜日

NSBlockOperation を使って並行処理

iOS 4 から追加された NSBlockOperation を使うと簡単に並行処理が書ける。

NSBlockOperation


NSBlockOperation Class Reference

NSBlockOperation は従来 -[NSOperation main] に実装していた処理を Blocksで書くことができる。こんな感じ。
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock: ^{
    // 処理1
}];

NSOperation のサブクラスなので実行開始は start を投げてやればよい。
[operation start];
複数の処理を走らせたい時は addExecutionBlock: で処理 Blocksを追加していく。
[operation addExecutionBlock:^{
     // 処理2
}];
終了時に処理を実行したい場合は iOS4 から NSOperation に追加された setCompletionBlock: で処理を指定しておくことができる。
NSOperation Class Reference - setCompletionBlock:

[operation setCompletionBlock:^{
    // 終了時処理
}];
なお start で実行した後は、すべての処理が完了するまでその場でブロックされる。


サンプル


NSBlockOperation を使ったサンプルプログラムを作って動きを確かめてみた。

実行すると2つの処理が同時に走る。thread1は1秒間隔でラベルを更新していく。thread2の方は2秒間隔で更新する。

ソースコードはこんな感じ。
- (IBAction)start:(id)sender
{
 NSLog(@"setup");

 NSLog(@"1: %@", [NSThread currentThread]);
 NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock: ^{
  NSLog(@"2: %@", [NSThread currentThread]);
  for (int i=0; i < 10; i++) {
   [label performSelectorInBackground:@selector(setText:)
         withObject:[NSString stringWithFormat:@"running: %d", i+1]];
   [NSThread sleepForTimeInterval:1.0];
  }
 }];

 [operation addExecutionBlock:^{
  NSLog(@"3: %@", [NSThread currentThread]);
  for (int i=0; i < 5; i++) {
   [label2 performSelectorInBackground:@selector(setText:)
         withObject:[NSString stringWithFormat:@"running: %d", i+1]];
   [NSThread sleepForTimeInterval:2.0];
  }
 }];

 [operation setCompletionBlock:^{
  NSLog(@"completion");
  label.text = @"completion";
  label2.text = @"completion";
 }];

 NSLog(@"start");
 [operation start];
 
 NSLog(@"end");
}
UILabelの更新はメインスレッドで行う必要があるため -[NSObject performSelectorInBackground:withObject:] を使っている。

動作中のコンソール:
setup
1: <NSThread: 0x590db20>{name = (null), num = 1}
start
2: <NSThread: 0x590db20>{name = (null), num = 1}
 3: <NSThread: 0x5d36840>{name = (null), num = 3}
end
 4: <NSThread: 0x5d36840>{name = (null), num = 3}

これを見ると次のことがわかる。
  1. 1番目の処理はメインスレッドで走っている(NSThreadが同じ)
  2. すべての処理が完了するまでブロックされる
  3. completion処理はブロック解除後に非メインスレッドで実行される

最後の動作が腑に落ちない感じもするがそういうものなのだろう。

ソースは GitHub からどうぞ。
WaitScreenSample at 2010-09-05 from xcatsan's iOS-Sample-Code - GitHub


実機での動作


ここまでは iPhone シミュレータでの話。
実機で走らせるとまた違った動作になった。
setup
1: <NSThread: 0x105210>{name = (null), num = 1}
start
2: <NSThread: 0x105210>{name = (null), num = 1}
3: <NSThread: 0x105210>{name = (null), num = 1}
end
 4: <NSThread: 0x151820>{name = (null), num = 18}

メインスレッドだけで処理が走っている?実際画面上の動作を見ても thread1 のカウントアップが終了した後、thread2のカウントアップが走る。同時には動作しない。

うーむなぜだろう。書き方が悪いのか?
...今日は時間切れ。



参考情報

Concurrency Programming Guide: Operation Queues (日本語訳) - steelwheelsの日記
Concurrency Programming Guide: Introduction
Using Concurrency To Improve Responsiveness
Togetter - 「【技術情報】iOS 4のNSOperationはGCD使用か否かの俺様用まとめ」

0 件のコメント:

コメントを投稿