Grand Central Dispatchについて日本語で丁寧に詳しく説明してくれるサイトはないかなぁと待っていたのですが、待っている間にひととおり自分なりに調べ終えてしまったので、まとめて書いておきます。まちがいに気がついたらご指摘いただけるとうれしいです。
Grand Central Dispatch(以下ディスパッチと書きます。リファレンスとか関数名とかGrand Centralって全然ついていなくてDispatchだけですし。)とはなんぞやというと、Snow Leopardから導入された並列処理のAPIです。並列処理といえばLeopardではNSOperationというObjective-CのAPIが導入されましたが、ディスパッチはC言語のAPIとして用意されていて、システムに近い低レベルなところで使えるものです。
あれこれ試した印象で言うと、コアが2つしかないCPUのMacでたいした事の無い処理をなんでもかんでもディスパッチで並列にしても、ディスパッチのオーバーヘッドが大きくて逆に遅くなってしまうので、どこで使うかってのはよく考えた方が良さそうです。Mac Proみたいにコアがたくさんあると効果が大きいんだろうなぁと思います。
Blockの構文
ディスパッチでは一つ一つの処理をブロックという単位でキューに渡して処理を実行します。ブロックの代わりに関数を使う事も出来るのですが、あえてこのタイミングでC言語を拡張して用意しているくらいですので、ブロックを使うというのが正当な使い方だと思います。
ブロックは変数に入れておく事ができ、ブロック名をname1とname2とすると宣言はこうなります。
void (^name1)(void); //返り値なし、引数なし int (^name2)(float, int); //返り値int、引数floatとint
名前の頭にキャレット(^)を付けます。で、これらへ代入をするのはこうなります。
name1 = ^{ //ブロックの処理を記述 }; name2 = ^(float f, int i) { //ブロックの処理を記述 return (int)f + i; };
ブロック本体は、キャレット(^)、引数、その後にブレース({})で囲んで記述します。name1のように引数がvoidなら省略できます。なお、name2のように返り値があったり、引数が2つ以上のブロックはディスパッチでは使いません。
ブロックは関数と違って、メソッドや関数の中に置けます。関数のような感じで外にも置けます。
呼び出す時は関数と一緒です。
name1(); name2(3.0, 5); //返りは8
ブロック変数
ディスパッチでブロックを使うときには返り値が無いとなると、処理結果を得るためにはどうするかって事なんですが、そのひとつにブロック変数というのがあります。たとえば、以下のようなコード。
#import <Foundation/Foundation.h> int y = 0; int main (int argc, const char * argv[]) { int x = 0; void (^inc)(void) = ^{ x++; //エラー y++; }; inc(); printf("x = %d / y = %d\n", x, y); return 0; }
これはx++のところでエラーが出てコンパイルが通りません。
ブロック内では、グローバル変数には自由にアクセスできますが、関数内のブロック外にあるローカル変数は、そのままでは読み込めるだけで書き込みは出来ません。ディスパッチでブロックを処理するときには、呼び出した関数とは別のスレッドで処理される場合があるので(というかそれがディスパッチを使う目的)、関数が終わったら消えてしまうローカル変数には戻せないという事だと思います。
ローカル変数に書き込むには__blockという修飾子をつけます。こうすることで、その変数が使われるブロックがすべて終わるまで保持されるようです。
#import <Foundation/Foundation.h> int y = 0; int main (int argc, const char * argv[]) { __block int x = 0; void (^inc)(void) = ^{ x++; y++; }; inc(); printf("x = %d / y = %d\n", x, y); // x = 1、y = 1 return 0; }
とりあえずブロックはこんなところで、次回は実際にディスパッチを使ってみます。