書こう書こうと思ってたけど忘れてたのを、PerlエンジニアのためのObjective-C Blocks入門を見て思い出した。すいませんタイトルは便乗です。

試しに書いてみる

Blocksってのが何者なのかはさっきの記事なりAppleのドキュメントなりを見てもらえばいいと思うんですが、まぁウケが良さそうなので付けたタイトルにのっとってRubyと比較してみる。

f = lambda {|x|
  puts "#{x}のこと以外は何も考えられない"
}
f.call("うどん")
void (^f)(id) = ^(id x) {
  NSLog(@"世界の全てを敵に回しても、僕は%@の味方だ", x);
};
f(@"うどん");

なんだ、そっくりじゃない。似てる似てる。

上がRuby版、下がObjC版。下は普通引数にはNSString*とかを使うと思うけど、まぁRuby版と挙動を合わせるためにidにしてみた。それぞれコピペして動かしてみるといいよ。好きな子の名前とか入れてみるといいよ。

なに?ブロックを変数に入れてるとこの宣言がキモい?じゃあこうだ。こうすればいい。

id f = ^(id x) {
  NSLog(@"世界の全てを敵に回しても、僕は%@の味方だ", x);
};

id型の変数に代入できてしまった。実はこのブロックはオブジェクトなので、普通にid型として扱えるし、メッセージのレシーバになったりできる。もちろんid型の変数に入ってるときは関数みたいに使ったりできないので、実際使うときは

((void (^)(id))f)(@"うどん");

とかキャストしてやらなきゃいけないわけで、id型で宣言する意味はあんまりない。ただ、id型として扱えるってことは、NSArrayとかNSDictionaryにつっこんどいたりできるってことでもある。コレクションクラスとか自前で作らなくても他のオブジェクトと一緒にArrayにつっこんどいてうまいこと処理するとかできる。ところでObjC上ではObjCのオブジェクト扱いだけど、Cで使ってるときはこれ何として扱われてるんだろう。気になる。

せっかくなのでブロック構文

これだけだと何それおいしいので終わりなので、実際に使ってるところをみてみる。Rubyでblockって言ったらブロック構文を思い浮かべると思う。例えばこんなの。

languages = ['Ruby','Objective-C','Perl','PHP','JavaScript','Haskell']
languages.each do |l|
  puts "#{l}なら多分書ける"
end

Blocksと言うからにはこういう使い方をするメソッドがいくつかFoundationのクラスにもある。上記のコードをObjCでブロックを使って書くとこんなかんじになる。

NSArray *languages = [NSArray arrayWithObjects:@"Ruby",@"Objective-C",@"Perl",@"PHP",@"JavaScript",@"Haskell",nil];
[languages enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
  NSLog(@"%@なら多分書ける", item);
}];

う、うん、似てる…よね?ちょっとわかり辛いかもだけど enumerateObjectsUsingBlock:ってのが引数にブロックを取るNSArrayのインスタンスメソッドで、Array#eachと同じように自分自身の要素を一個ずつ順にブロックに渡して実行してくれる。ちなみに、「最初の引数がNSArrayの要素のオブジェクト、次がインデックスの整数値、最後がループを止めるためのBOOLのポインタ」という3引数のブロックを渡さないといけない。どっちかっつーとEnumerable#each_with_indexのが近いかな。

上の例ではitemしか使ってないけど、全部使うとしたらこんな感じ。3個目の引数にYESを入れてやるとそれ以降は実行されなくなる。

[languages enumerateObjectsUsingBlock:
  ^(id item, NSUInteger index, BOOL *stop) {
    NSString *status;
    if ([item isEqualToString:@"PHP"]) {
      status = @"アタシはしんだ。";
      *stop  = YES;
    } else {
      status = @"楽しかった。";
    }
    NSLog(@"%d日目。%@を書いた。%@", index+1, item, status);
  }
];

や、別にPHPについて何か言いたいわけじゃないですよ?やだなぁ、ちょっとした冗談じゃないですか。

今度書く

この記事の例では書いてないけど、所謂クロージャなのでブロックが生成されたコンテキストの変数とかブロックの中から参照できる。NSArrayにmapメソッド生やしてみたり簡易ベンチマーク関数作ってみたりしたので今度晒す。

あと、Enumeratorもいいけど、Blocksが真の力を発揮するのは、Cならdispatch_async、ObjCならNSOperationとかを使って非同期に処理をぽんぽん投げてくとき。というか、BlocksがそもそもGrand Central Dispatchという仕組みと一緒に導入されたものなので、それについてはもうちょい調べて今度書く。ちなみに例えばNSArrayでもenumerateObjectsUsingBlock:の他にenumerateObjectsWithOptions:usingBlock:というメソッドがあって、これにNSEnumerationConcurrentってオプションを渡してやると並列に実行してくれたりする。

余談

構文がキモいのはもうObjCだしどうしようもないけど、気に入らなければtypedefしておいた上でブロック生成のマクロでも書いとくと少しはマシに見えなくもないような気がしなくもないような感じがしたりしなかったり。うん。キモい。

余談2

gccだと独自拡張らしいので多分使えるのMacOSX 10.6のXcodeに付属してるやつだけだと思うけど、Clangには組み込まれてるらしい。試してないけど、Clangでなら他のプラットフォームでもBlocks使ったCのコードコンパイルできるんじゃないかな。

余談3

iPhoneSDKで使えるのかい?ってのは聞かないでくだしあ。泣きたい。