-
Notifications
You must be signed in to change notification settings - Fork 336
8.1 Blocks
BlocksはiOS4.0以降で使うことの出来る機能で、他のプログラミング言語ではクロージャやラムダ式と呼ばれるものです。 通信完了後に実行したい、アニメーション完了後に実行したい、あるいは配列のソートを行う時の順序の判定として使いたい、などのように様々な箇所で用いることができます。この章では、このBlocksの使い方と注意すべきポイントについて説明します。
Blocksの構文は以下のように書きます。
^戻り値の型(引数,..){ 式 }
と書いても、分かりづらいのでいくつか例を紹介します。
^BOOL(int a){ return a < 0; } // 基本的な使い方1
^int(NSString *str){ return str.length; } // 基本的な使い方2
// 返り値のないものも可能
^void(NSString *str){ NSLog("%@", str);}
^(NSString *str){ NSLog("%@", str);} // 返り値がない場合は省略できる
// 引数を取らない場合
^int(void){ return 10; }
// 返り値も引数もない場合
^void(void){ NSLog(@"blocks"); }
^{ NSLog(@"blocks"); } // 引数も返り値も省略できる
ちなみにこのコードは、コード中で
10;
と書くようなもので意味はありません。
Block構文をBlock型として宣言された変数に代入することもできます。Blockの実行はC言語における関数と同じように実行することができます。
void (^b)(int); // 戻り値がvoid型で引数としてint型の変数を取るblock型変数bの宣言
b = ^void(int a){ NSLog(@"%d", a); }; // b へ blockを代入
b(10); // bに代入されたblockを実行
BOOL (^b1)(int, int); // 戻り値がBOOL型で引数として二つのint型を取るblock型変数b1の宣言
b1 = ^BOOL(int x, int y){ return x < y; }; // block構文を変数へ代入
BOOL result = b1(10, 20); // block型変数b1を実行。resultにはYESが入る。
Blocksの実装はC言語に対する拡張機能のため、Block型の変数はC言語の変数と全く同じように使用することができます。 自動変数、static変数、グローバル変数などに使うことができます。
また、関数の引数, 戻り値としても利用することができます。
void func1( int (^tmp)(int) ) { // 引数として利用
tmp(10);
}
// "int型を引数としてとり、double型"を返すBlock型を返り値として、float型の引数aを取る関数func2の定義
double (^func2(float a))(int) {
return ^double(int b){ return a + b;};
}
// func2の使い方 //
double ^(my_block)(int) = func2(10);
double val = my_block(20); // val = 10 + 20 が代入される
このBlock構文は複雑なため、ソースコードの可読性を下げてしまうなどの問題を引き起こしてしまいます。そのため、C言語の関数ポインタと同様にtypedefを用いて型宣言を行うことがよく行われます。
typedef 戻り値の型 ^(型の名前)(引数列)
例えば上記func1の場合、
typedef int (^blk1_t)(int)
とすることで
void func1( int (^tmp)(int) ) { // 引数として利用
tmp(10);
}
↓
void func1(blk1_t tmp) {
tmp(10);
}
とすることができます。
func2の場合,
typedef double (^blk2_t)(int)
とすることで、上記のコードは
blk2_t func2(float a){
return ^double (int b){ return a + b};
}
// func2の使い方 //
blk2_t my_block = func2(10);
double val = my_block(20); // val = 10 + 20 が代入される
と書き直すことができます。
クロージャやラムダ式同様、Blocksも自動変数の値をキャプチャして取り込むことができます。
int a = 10;
void (^b1)() = ^{NSLog(@"a = %d", a);};
b1(); // コンソールに a = 10 と表示される
変数の値のキャプチャは、Block構文が実行されるタイミングに行われ、その時の変数の値がBlockの中では使われます。 例えば
int a = 10;
void (^b1)() = ^{NSLog(@"a = %d", a);};
b1(); // a = 10 と表示
a = 20;
b1(); // a = 10 と表示
このようにBlockオブジェクトを変数b1に代入した後にaの値を変化させても、Block内での値は変化しません
int a = 10;
void (^b1)() = ^{NSLog(@"a = %d", a);};
b1(); // コンソールに a = 10 と表示される
a = 20;
b1 = ^{NSLog(@"a = %d", a);};
b1(); // コンソールに a = 20 と表示される
aの値を変化させたあとに、もう一度Blockオブジェクトを代入すると、もちろん結果も変わります。Blockオブジェクトが実行されるタイミングでの値がキャプチャされます
キャプチャした変数は、値を覗くことは出来ても書き換えることは出来ません。たとえば以下のようなコードをコンパイルすると
int a = 10;
void (^b1)() = ^{
a = 20;
};
次のようにエラーが出ます。
Variable is not assignable (missing __block type specifier)
Block内でキャプチャした変数の書き換えを行うには、変数宣言の際に __block
指定子をつけます。
__block int a = 10;
void (^b1)() = ^{
a = 20;
NSLog(@"a = %d", a);
};
b1(); // コンソールに a = 20 と表示される
NSLog(@"a = %d", a); // ちなみにここでも a = 20 と表示されます
Blockの中でNSMutableArrayなどを操作したらどうなるでしょうか
NSMutableArray *array = [NSMutableArray arrayWithArray:@[@"hoge", @"fuga"]];
void (^b2)() = ^{
[array addObject:@"piyo"];
};
b2();
NSLog(@"%@", array); // @"hoge", @"fuga", @"piyo"
エラーも発生せず、さらに三つ目のオブジェクト,"piyo"も代入されています。 このソースコードでキャプチャした自動変数はNSMutableArrayクラスのオブジェクトです。よりプリミティブな表現では、arrayはNSMutableArrayクラスの構造体へのポインタを指しています。そのため、array自体への代入はエラーとなりますが、キャプチャした自動変数を書き換えている訳ではないのでエラーとはなりません。
あるクラスのプロパティとしてBlockオブジェクトを持たせることはありえると思います。例えばjob queueのクラスを作った際にタスクをBlockで記述する、何かのタスクの実行完了後の処理をBlockで記述する、などのことがあり得ると思います。
そのようなとき、Blockプロパティからselfを参照すると循環参照に陥る危険性があります。
例として、次のようなクラスを考えてみます。
typedef void (^blk_t)();
@interface BlockSample : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) blk_t name_print_block;
@end
プロパティとしてnameと、シンプルなBlockオブジェクトを持ちます。 実装を以下のようにしてみました。
@implementation BlockSample
- (id)init
{
self = [super init];
if (self) {
_name_print_block = ^{
NSLog(@"%@", self.name);
};
}
return self;
}
@end
name_print_blockでは、self.nameを出力するようにしています。 この状態でコンパイルすると、次のような警告が出ます。
Capturing 'self' strongly in this block is likely to lead to a retain cycle
これは次のことに起因します。
BlockSampleクラスはstrongプロパティとしてBlockを持っています。
@property (nonatomic, strong) blk_t name_print_block;
Blockが自動変数をキャプチャするタイミングで、オブジェクトはBlockから所有されます。
BlockSampleのインスタンスがBlockを所有し、キャプチャのタイミングでBlockオブジェクトがインスタンスを所有するために循環参照が発生します。
この循環参照を避けるためには、delegateメソッドパターンなどで用いられるように片方の参照をstrongからweakに変えてやればOKです。つまり、インスタンスとBlockのうち片方は所有し、もう片方は参照だけ持つ、という形にすればいいのです。 プロパティをweakにしてしまうと、スコープを抜けた時に解放されてしまうので、Blockからの参照を弱参照にします。
そのためには、__weak指定子を用いて弱参照のオブジェクトをBlockの中から参照するようにします。
__weak BlockSample *weakSelf = self;
_name_print_block = ^{
NSLog(@"%@", weakSelf.name);
};
こうすることで、selfへの参照を弱参照にすることができ、循環参照を避けることができます。
※今回は説明の簡略化のために、Blocksの確保される領域については説明を省略しました。より正しくこの循環参照の問題について知りたい場合はこちらの本をお勧めします。
http://tatsu-zine.com/books/objc
perlにあるgrepやmapはNSArrayにはありません。grepを行うNSArrayの拡張 NSArray(grep)を作ってください。
使用例
NSArray *before = @[ @"hoge", @"fuga", @"piyo", @1, @2, @3, @"foo", @"bar"];
NSArray *after = [before grep:^BOOL(id obj) {
return [obj isKindOfClass:[NSString class]];
}];
NSLog(@"%@", after); // @"hoge", @"fuga", @"piyo", @"foo", @"bar"
はじめに
-
導入
-
1.3 UIViewController1 UIViewController のカスタマイズ(xib, autoresizing)
-
UIKit 1 - container, rotate-
-
UIKit 2- UIView -
-
UIKit 3 - table view -
-
UIKit 4 - image and text -
-
ネットワーク処理
-
ローカルキャッシュと通知
-
Blocks, GCD
-
設計とデザインパターン
-
開発ツール
-
テスト
-
In-App Purchase
-
付録