備忘録

開発でつまったところの備忘録.不定期に更新します.

ブロックオブジェクトまとめ

ブロックオブジェクトはObjective-CではなくC言語の機能として実装されています。

他のプログラミング言語ではクロージャとして知られている言語機能に相当します。

・定義(ブロックリテラル

  ^( 引数列 ) { 本体 }

・変数宣言

  返り値の型 (^ブロック変数) (引数の型) ;

・変数宣言+代入の例

  void ( ^b) ( int ) = ^ (int i) { printf ("%d¥n" , i ) ; } ;

・実行

  b ( 5 ) ;

引数がない場合は ( void ) , ( ) , 省略 の3パターンで書けます。

このように関数ポインタのように使うことができます。ブロックリテラルが返り値の型を明示しないで記述されることは異なっています。

 

ブロックオブジェクトに含まれる変数の振る舞いには注意が必要で、ブロックオブジェクトはリテラルが記述された位置での自動変数の値を保存します。

static int s = 10 ;

int a = 10 ;

void (^block) (void) = ^{ printf ( "%d, %d¥n", s, a ); } ;

と宣言されたとき、この後に

s = 0 ;

a = 0 ;

block ;

としても

0 10

となり、静的変数はかわりますが、自動変数はかわりません。

外部変数および静的変数はブロックオブジェクトの内部から変数自体にアクセスしていますが、自動変数はリテラルが記述された次点での値が保存されていて、それが参照されるからです。

従って、元の変数の値が変化しても評価には反映されず、保存された変数は参照できますが、変更できません。ブロック内部で変更しようとするとエラーがおこります。

 

関数ポインタではなく、ブロックを使うことの利点として、このブロックリテラルが記述される直前の情報を簡単に利用することができることがあげられます。関数を呼び出す箇所で行いたいことをその位置にそこの情報を利用して記述できます。

関数ポインタだとわざわざ引数として渡さなければならず、また構造体や関数をそれぞれ別の場所に記述しなければならず拡張性や分かりやすさで劣ります。

 

ブロックリテラルを関数の外側に記述した場合、ブロックオブジェクトのメモリ領域は静的なデータ領域に1つだけ作られます。

それに対して関数の内側に記述した場合、メモリ領域は自動変数と同様にそれを含む関数が実行される際にスタック上に用意され、メモリ領域は実行されている間だけ存在します。

 

関数内でスタックの状態とは無関係にブロックオブジェクトを扱いたいときは

Block_copy( block )

で、引数のブロックオブジェクトの複製をヒープ領域に作って返すことができます。

これを使うと

for ( i = 0 ; i < 10 ; i++ )

    blocks[ i ] = Block_copy ( ^ { return i ; }  ) ;

のようなことが可能です(スタックだと全てi = 9が代入される)。

 

ブロックオブジェクトでは自動変数の値を変更することができないと説明しましたが、__block修飾子を自動変数に指定することで、それを参照するブロックオブジェクトから読み書き可能になり、また同じ変数スコープ無いの複数ブロックオブジェクトから参照されている場合、それらの間で値が共有されます。

__block変数は静的な変数ではないので、宣言に伴って動的に生成されます。同一の__block変数を参照しているブロックオブジェクトが複数存在していた場合、そのうち1つでも存在し続ければ__block変数も存在し続けます。

 

メソッド定義内のブロックリテラルインスタンス変数が記述されている場合、インスタンス変数には直接アクセスでき、値を変更することもできます。

 

ちなみに前にC言語として実装されていると書きましたが、Objective-Cで内ではObjective-Cのオブジェクトとして実装され、メソッドもあるようです。

 

とりあえずこんなところで。