Objective-Cのブロックの書き方整理
何回書いても忘れてしまうものの一つにObjective-Cのブロックオブジェクトがあります。何回書いても忘れ、その度に検索したり昔書いたコードを見直してたり。
そんな生活に疲れたのでまとめました。スニペットとかに登録しておくといいかもしれないですね。グダグダ書いてもしょうがないので、書き方のパターンをまとめました。
ブロックリテラル
^( /* 引数列 */ ) { /* 本体 */ };
宣言
// 戻り値の型 (^ブロック変数名)(引数リスト)
void (^myFunction)(int);
ブロックの型を定義してブロックオブジェクトに代入
// typedef 戻り値の型 (^ブロックの型名)(引数リスト)
typedef void (^MyFunction)(int);
Objective-Cのメソッドの引数にブロックオブジェクトを使用
// 戻り値の型 (^)(引数リスト)ブロック名
- (void)checkWithBlock:(BOOL (^)(int i, int j))block;
typedefしたものをObjective-Cのメソッドの引数にする
// 戻り値の型 (^)(引数リスト)ブロック名
typedef BOOL (^MyFunction2)(int, int);
- (void)checkWithBlock:(MyFunction2)block;
ブロックの文法
上記のものをまとめました。
リテラル
先頭に^
(ハット)を書き、()
で囲み、処理は{}
に書きます。;
を忘れずに!
^( /* 引数列 */ ) { /* 本体 */ };
ブロックオブジェクトはオブジェクトなので、引数として渡したり、変数に代入したり、プロパティとして保持させたりもできます。
宣言
myFunction
という変数のブロックオブジェクトの宣言は以下のようになります。
// 宣言
void (^myFunction)(int);
/* 戻り値 (^変数名)(引数リスト)*/
で、この変数myFunction
に対してブロックリテラルを代入するとこうです。
void (^myFunction)(int) = ^(int i){ printf("%d\n",i); };
このブロックを呼び出すときはこのように書きます。
myFunction(3); // 3
typdef
typdef
を使ってブロックオブジェクトの型を定義することができます。
// ブロックの型宣言
typedef void (^MyFunction)(int);
/* typedef 戻り値の型 (^ブロックの型名)(引数リスト); */
戻り値なしでint型の引数を取るmyFunction
型のブロックオブジェクトの変数の定義となります。
typedef void (^MyFunction)(int);
MyFunction myFunc = ^(int i){
NSLog(@"%d",i);
};
myFunc(5); //5が出力される
型として定義しておくことで再利用がしやすくなりますね。
Objective-Cのメソッドの引数にブロックオブジェクトを渡す
// 引数にブロックオブジェクト
- (void)setBlock:(BOOL (^)(int i, int j))block;
// 戻り値の型 (^)(引数リスト)ブロック名
// hogeメソッドの戻り値として"BOOLを戻り値とするブロックオブジェクト"を返す
- (BOOL (^)(int, int)) hoge;
- // 戻り値の型 (^)(引数リスト)
おそらくこの書き方と前述の型宣言やブロックリテラルの書き方が異なるため混乱してしまう人が多いように感じます。ややこしいですよね。
ブロックに関する注意事項など
以下は注意事項です。メモ書き程度なのであまりまとまっていないです。
クロージャとして振る舞うブロックオブジェクト
ブロックオブジェクトで覚えておくべきことはコレです。
「ブロックリテラルは記述された位置での自動変数の値を保存している」
詳解 Objective-C 2.0 第3版 p345より
ブロックオブジェクトは記述が行われた時点のアクセス可能な変数を丸ごと囲んでしまう(クローズな状態にしてしまう=クロージャ)。パッキング、キャプチャとか言います。
キャプチャ
ただし、キャプチャされた自動変数は書き換えができないので注意してください。
たとえば以下の例では、value
変数の値を書き換えて想定した値になることを期待しますが、
typedef void (^MyFunction)(int);
int value = 1;
MyFunction myFunc = ^(int i){
NSLog(@"%d", i*value);
};
value = 5; // "25"と出力されることを期待
myFunc(5); // value変数は1でキャプチャされているので"5"と出力される
myFunc
が作られた時点で変数i
がキャプチャされているため、想定した通りになりません。
注意点
1.外部変数、static変数 → 外部から変数自体を直接参照できて書き換えも可能
2.自動変数 → ブロックリテラルが記述された時点の値が保存されていて、それを参照する。このキャプチャされた変数の書き換えはできない
キャプチャされた自動変数は変更はできません。実行時はconst修飾子を付けた変数と同じ扱いになります。
まとめ
Swiftだとクロージャが簡単にかけるので、なおさらObjective-Cのブロックが分かりづらく感じてしまいますね。
Swiftに移行しているところもチラホラあると思いますが、まだまだObjective-Cのコードを見ることは多いと思います。
とはいえ書き方さえわかればかなり便利なものなので、しっかりと覚えておきたいですね。
覚えられないからまとめたんだけど。