As Sloth As Possible

可能な限りナマケモノでありたい

タグ:Blocks

前の記事で予告した通り、今度はNSRegularExpressionの話。

正規表現でマッチした部分文字列を取得する

まずNSRegularExpressionオブジェクトを作って、それのメソッドにNSStringのオブジェクトを渡す、という形で使う。まぁ説明するよりコード見た方が早い。

NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」";
NSError *error   = nil;
NSRegularExpression *regexp =
  [NSRegularExpression regularExpressionWithPattern:@"「そんな(.+)で大丈夫か?」「(.+)」"
                                            options:0
                                              error:&error];
if (error != nil) {
  NSLog(@"%@", error);
} else {
  NSTextCheckingResult *match =
    [regexp firstMatchInString:string options:0 range:NSMakeRange(0, string.length)];
  NSLog(@"%d", match.numberOfRanges); // 3のはず
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]); // マッチした文字列全部
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]); // "正規表現"
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]); // "大丈夫だ、問題ない"
}

地味にややこしい。Rubyで書いたらこんなんで済むのに。

# coding: utf-8
if "「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」" =~ /「そんな(.+)で大丈夫か?」「(.+)」/
  puts $&
  puts $1
  puts $2
end

まぁRubyやPerlと比べるのは(少なくとも文字列操作や正規表現に関して言えば)フェアじゃないですけど!とにかくこれで正規表現で部分文字列を探せるようになりました、と。

ちなみに、-firstMatchInString:options:range:というメソッド名で分かると思うけど、これは最初にマッチした箇所しか取ってこない。マッチした箇所全て欲しければ、-matchesInString:options:range:を使えば、NSTextCheckingResultが入ったNSArrayが返ってくる。別に返り値はずっと取っておく必要はなくて、単にマッチする毎になんか処理をしたいんだよ、ってときは、-enumerateMatchesInString:options:range:usingBlock:が使える。さっきの-firstMatchInString:options:range:を書き換えるとこんな感じになる。

NSRegularExpressionOptions options = 0;
NSRange range = NSMakeRange(0, string.length);
id block = ^(NSTextCheckingResult *match, NSMatchingFlags flag, BOOL *stop){
  NSLog(@"%d", match.numberOfRanges);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]);
};
[regexp enumerateMatchesInString:string options:options range:range usingBlock:block];

Blocksの使い方は以前書いた記事とか読んでもらえると分かるかもしれない。あの記事を書いた時点ではiOS4.0を想定してアプリ作れなかったので実質まともに使えるのがSnow Leopardだけだったのだけど、今ならiPhone/iPadともに4系前提で作れるし、そもそもNSRegularExpression自体がiOS4.0以降にしか無いのでNSRegularExpressionを使える環境ならBlockも使えるので問題ない。

置換する

正規表現が使えるなら一番やりたいのは置換だろう、ということでもちろん置換もできる。-stringByReplacingMatchesInString:options:range:withTemplate:というのがそれ。

  NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」";
  NSString *template =
    @"$0\n→($2砕け散る)\n→「神は言っている、ここで死ぬ運命ではないと」\n→「$1」「一番いいのを頼む」";
  NSRegularExpression *regexp =
    [NSRegularExpression regularExpressionWithPattern:@"「(そんな(.+)で大丈夫か?)」「.+」"
                                              options:0
                                                error:nil];
  NSString *replaced =
    [regexp stringByReplacingMatchesInString:string
                                     options:0
                                       range:NSMakeRange(0,string.length)
                                withTemplate:template];
  NSLog(@"%@",replaced);

最初は話を聞かなかったあいつもちゃんと一番いいのを頼んできたので、今度は大丈夫だろう。しれっと$0とか$1とか使ってるけど、もちろんちゃんと置換文字列の中でキャプチャした部分文字列を参照したりできてるはず。

ただ、-stringByReplacingMatchesInString:options:range:withTemplate:は文字列そのものを置換してるわけじゃなくて、引数のNSStringのオブジェクトをcopyして置換したものを返してくる。なので、元のstringは何も変わってないので変わったつもりで使おうとしたらアレ?ってなるし、毎回文字列のコピーをするので場合によっては無駄になる。その場合は-replaceMatchesInString:options:range:withTemplate:の方を使う。基本的には-stringByReplacingMatchesInString:options:range:withTemplate:と同じなんだけど、以下の点が違う。

  • 引数にNSStringでは無くNSMutableStringを取る
  • 引数のオブジェクトのコピーではなく引数のオブジェクト自体を置換する
  • 返り値は置換後の文字列ではなく整数値で、置換箇所の数を返す

というわけで、ある正規表現で置換した文字列をさらに別な正規表現で置換して、みたいなことをやる場合はこっちのメソッドを使うべき。

ちなみに、上記二つのメソッドはマッチした箇所を全部置換する。例えば下のようなコードだと「大丈夫か」と「大丈夫だ、」が両方置換される。

  NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」";
  NSString *template =
    @"チョ☆チョニッシーナ☆まっソコぶれっシュ☆エスボグリバンバーベーコンさんだね!";
  NSRegularExpression *regexp =
    [NSRegularExpression regularExpressionWithPattern:@"大丈夫(か|だ)、?"
                                              options:0
                                                error:nil];
  NSString *replaced =
    [regexp stringByReplacingMatchesInString:string
                                     options:0
                                       range:NSMakeRange(0,string.length)
                                withTemplate:template];
  NSLog(@"%@",replaced);

もしマッチした箇所の内特定の部分だけを置換したい場合は、-firstMatchInString:options:range:とか-matchesInString:options:range:でNSTextCheckingResultのオブジェクトを取得しておいてから、-replacementStringForResult:inString:offset:template:を使う、みたいな感じになるかしら。ちょっと面倒な気もするけど。

RegexKitLite or NSRegularExpression

両方書いてみた感想で言うと、個人的にはRegexKitLiteのNSStringにメソッド生やしてくアプローチのAPIのが使い易いと思った。CoreFoundation使ってごりごり書いてるのでパフォーマンスも悪くないし、割と早い段階からBlocksに対応してたりとアクティブに開発されてるし、その気になればソース読めるし(まぁ、チラ見しては見たもののあんまり読む気にはならないのだけども)…とか考えると、既にRegexKitLiteを使ってるなら別に無理にNSRegularExpressionに乗り換える必要は無い気がしてくる。iOS4.0以前のバージョンもターゲットにするなら他に選択肢はないし、あと何故かNSRegularExpressionクラスはiOSにしか無くてMacOSXでは使えないという面白いことになってるので、iOSでもMacでも動くようなコードを書く場合もやっぱりNSRegularExpressionは使えない。

とは言えNSRegularExpressionの方はFoundationの一部なので、数カ所正規表現での置換を使いたいが為に外部のコード落としてきてプロジェクトに組み込んでlibicucoreに忘れずにリンクして…ってしないで済むなぁとか、万が一iOSの内部の実装が変わったりなんかの規約が変わったりしてもおそらく書き換えないで済むだろうなぁという多少の安心感とかはある。ので、これから作るアプリで、4.0以降のみをターゲットにしてる場合は、NSRegularExpressionを使って書こうかなぁなんて思ったりしてたり。

Rubyと比べながらBlocksをいじってみたりBlocksでNSArrayにmapメソッドを生やしてみたりしてきたので、そろそろGrand Central Dispatch(GCD)も試してみる。あんま関係ないけど、グランド・セントラル・ディスパッチってなんか必殺技っぽいよね。じゃあ一緒に高らかに叫んでみようか。せーの、グランド!セントラル!ディスパッチ!!

GCDってなにさ

ドキュメント嫁。

…だけだと流石に不親切なので、一応簡単に説明すると、APIを通してぽんぽん処理をqueueにつっこんでってやると、ランタイムの方でそれを上手いこと並列実行しといてやるよ!安心しろチェリーボーイ共、スレッドのことは俺が面倒見てやるぜ!って仕組み。そんな口調なのかどうかはわかんないけど、まぁ大体そんな感じ(適当)。

例によってRubyと比較

まぁ、こんなコードがあったとします。

# ruby
f = lambda {
  puts "0.25秒後から本気出す"
  sleep 0.25
}
t = Time.now
20.times do
  f.call
end
p Time.now - t

んで、それを例によってObjCでBlocksを使って書くとこんなコードになります。ちなみにどっちも別にBlocksやlambdaでやる必要は無いんだけど、この後のコードと比較の為にわざとそうしてるのでスルーしておくれやす。

# ObjC
void (^f)(void) = ^{
  NSLog(@"あと0.25秒だけ寝させてー");
  [NSThread sleepForTimeInterval:0.25];
};
NSDate d = [NSDate date];
unsigned int i = 20;
while (i--) {
  f();
}
NSLog(@"%f", [[NSDate date] timeIntervalSinceDate:d]);

まぁ見ての通り、標準出力に一言言ってから0.25秒sleepするだけの簡単なお仕事を20回ほどやってもらってます。あたり前のことだけど、0.25*20で5秒+αくらいの時間がかかるし、0.25秒っつってんのに5秒待たせるとか相当いい加減なやつだ。

んで、こんな風に、それぞれの処理が独立してるけど一個一個は結構時間かかる、みたいなのは、並列に実行させちゃったらいいんじゃね、みたいなことを偉い人は言いました。

# ruby
f = lambda {
  puts "0.25秒後から本気出す"
  sleep 0.25
}
t = Time.now
20.times do
  Thread.new { f.call }
end
(ThreadGroup::Default.list - [Thread.current]).each{|th|th.join}
p Time.now - t

まだ若干怠けてるけど、まぁ0.3秒行かない程度で終わる。5秒に比べたら一瞬みたいなもんだよね。

じゃあ次はそれをObjCで…と言いたいところなんだけど、ObjCで上のRubyのコードをNSThreadってクラスを使って書こうとすると、割と面倒い。特定のコンテキストを別スレッドで実行しようと思うと、detachするのにtargetとselector、つまりスレッドで実行されるオブジェクトとそいつから呼び出されるメソッドがなくちゃいけない。んでもって、作ったスレッドを自分で管理しなきゃいけない。一応適当なサンプルは書いたけど、あんまりこれをObjCで自前で書くことは無いと思う(理由は後述する)。

さて、GCDです

最初の方に言ったけど、大分ざっくり言うとGCDってのは「処理のブロックをキューにつっこんでってやると裏で上手いこと並列に処理してくれる」ものです。要は並列処理のめんどい感じを多少楽にしてくれるのがGCDの兄貴だってことです。兄貴なのか姉貴なのかは知りませんが。どっちかというと僕はお姉さんが好きですがどうでもいいことです。

# ObjC
dispatch_block_t block = ^{
  NSLog(@"あと0.25秒だけ寝させてー");
  [NSThread sleepForTimeInterval:0.25];
//  NSLog(@"%@", [NSThread currentThread]);
};
//NSLog(@"%@", [NSThread currentThread]);

NSDate d = [NSDate date];

// ここからGCD登場
dispacth_group_t group = disptach_group_create();
disptach_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
unsigned int i = 20;
while (i--) {
  disptch_group_async(group, queue, block);
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// ここまでGCDのお仕事

NSLog(@"%f", [[NSDate date] timeIntervalSinceDate:d]);

はい。Rubyの方をThreadを使って書き直したときと同じく、0.3秒行かないくらいの時間でさくっと処理してくれました。コメントアウトしてるのを戻せば、ちゃんと別々のスレッドで動いてるのも確認できると思います。ちなみにObjCの文法で書いてるとこを削れば普通にCでも使えます。使いたいときは#include <dispatch/dispatch.h>してください。

変わったところを解説すると、まずブロックの型がdispatch_block_tに変わってる。これは後で出てくるdispatch_group_asyncの引数の型なんだけど、void (^)(void)、つまり何も取らず何も返さないブロックって定義になってるので実はさっきと何も変わってない。

次にdisptach_group_createをしてgroupを作ってるけど、これはまぁ名前通り非同期に実行する処理をグルーピングするためのもの。Rubyの方でもThreadGroupが出てきたけど、あれと一緒で最後のdispatch_group_waitで一連の処理が全部終了するまで待ってやる為に使う。今回は全部の処理が終わるまでの時間が見たかったのと、アプリケーションとかじゃない普通のCUIのコマンドとして作ったときに何も考えずに非同期処理させるとdispatchしたのが終わる前にmainが終わっちゃうのでwaitする必要があったけど、GUIアプリケーションとかデーモンとかだとその心配はないのでグルーピングせずに単にdispatch_asyncしちゃってもいい。

んでここからが本質、dispatch_get_global_queueとdispatch_async(またはdispatch_group_async)。といっても別に大したことではなくて、

  1. queueを用意します
  2. dispatch_asyncにqueueとblockを渡します
  3. あとは裏でよしなにやってくれます

以上。中では「システムの負荷を見てスレッドを作るか待つか決める」「スレッドが一個も空いてなければ作るけど、さぼってるやつがいたら再利用する」「あっちこっちから放り込まれたブロックをどのスレッドに割り当てたら効率良いか考える」とか色々やってんだけど、使う側としてはそんなこと気にする必要無いし、それどころかスレッドが作られてることすら隠蔽されてる。やったのは単に関数にブロックを渡しただけ。ゆとりの僕でもできる簡単なお仕事です。ちなみにdispatchしてwaitするあたりの処理を続けて何回も実行すると、ちゃんとスレッド再利用してるのが確認できる。

他にも、globalって名前が付いた関数があるからにはglobalじゃないqueueを作る関数もあるとか、asyncって名前が付いた関数があるからにはsyncして実行する関数もあるとか、メインスレッドで動作するqueueがあるとか、さっきのコードではわざわざforループ回したけどループにはループ専用のdispatch_applyがあるとか、まぁ色々あるんだけど、Xcodeのあのアホみたいに使い辛いドキュメントビューワの検索窓にdispatch_って入れてやるといっぱい出てくるので見てみてくれればいいかと思います。

でもそれCじゃん

ええ。ここまでは誰がどうみてもCの関数、っていうかさっきも書いた通り実際<dispatch/dispatch.h>をincludeすればCでも使えるAPIです。いやさ、確かにObjCの文法はキモいけどさ、せっかくObjCで書いてるのにCの関数使うってどうなのよって?ご心配なく。ObjCならObjC流に実装する方法はもちろんある。

「並列実行」「どんどんキューにつっこむ」「スレッドの面倒はキューが見てくれる」あたりで、LeopardまでのOSXとかiPhoneSDKとかでアプリを書いたことある人は「それNSOperationとNSOperationQueueでできるじゃん」と思ったはず。これもキューにオペレーションオブジェクトをどんどんつっこんで行けば非同期でよしなにやってくれるクラスで、それ自体はGCDやBlocksとは関係なく前から使える。ので、もともとあんまり自前でNSThreadを作ったり管理したりはやったことなかった。じゃあObjCだと何も変わらないじゃんと思ったけど、Blocksが導入されたことでNSArrayやNSDictionary同様便利な機能が追加されているという。

# ObjC
void (^block)(void) = ^{
  NSLog(@"あと0.25秒だけ寝させてー");
  [NSThread sleepForTimeInterval:0.25];
//  NSLog(@"%@", [NSThread currentThread]);
};

//NSLog(@"%@", [NSThread currentThread]);
NSDate d = [NSDate date];

// こっからNSOpera(ry
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
unsigned int n = 20;
while (n--) {
  [queue addOperationWithBlock:block];
}
[qeueu waitUntilAllOperationsAreFinished];
// ここまでNSOpera(ry

NSLog(@"%f", [[NSDate date] timeIntervalSinceDate:d]);

おお、これは便利だな。Blocsを使わない場合は事前にNSOperationクラスを継承して独自のOperationクラスを作っとくとか、NSInvocationOperationを使うとかしてたところを、-[NSOperationQueue addOperationWithBlock:]を使えばブロックを渡してやるだけで非同期実行できちゃう。ブロックをひとまとめにしてオペレーションにしてくれるNSBlockOperationってクラスや、オペレーションが終了した後に実行される処理をブロックで設定できる-[NSOperation setCompletionBlock:]ってメソッドが追加されてて、「処理を一括りにして並列実行する」コードが大分書きやすくなっている。

MacRuby

RubyとObjCの話をしてるのにMacRubyさんを完全にスルーするという素敵なプレイをやってのけてきたわけですが、実はMacRubyさんはとっくにGCDに対応してやがります(MacRuby » An Introduction to GCD with MacRuby)。凄いな、ほんと、どこまで行くんだろう。

ついでに

Blocks入門NSArrayにmap生やしたのとこの記事のコードをのサンプルはgistに上げてみたので、まぁ一応一通り動く例になってるはず。あーあと簡易ベンチマーククラスみたいのも作ってみたので入れといた。適当なので実用するのはおすすめしません。Snow Leopardでrubyとrakeが入ってる環境なら、cloneしてきてrakeすればコンパイルできるはず。

参考記事

RubyエンジニアのためのObjective-C Blocks入門に引き続き、Blocksネタ。そっちの記事ではBlocksはクロージャ的ななにかだって言ってるのに単なる関数ポインタみたいにしか使ってなかったので、せっかくなのでクロージャ的に使ってみる。

eachできるならmapも欲しい

-[NSArray enumerateObjectsUsingBlock:]を使えば、Array#each相当のことができるとこまでは前回の記事でできた。そうすると、Array#map相当のこともNSArrayにさせられるはず。例えばこんなの。

# ruby
array_a = %w(ひたぎ 真宵 駿河 撫子 翼)
array_b = array_a.map {|x| "#{x}が可愛過ぎて生きるのが辛い" }

Rubyistには説明の必要もないと思うけど、Array#mapが何をしてるかというと、

  1. 1引数のブロックを受けて
  2. 自分自身の要素を一つずつブロックに渡して
  3. その返り値を要素にした新しいArrayを作って返す(※自分自身は変更しない)

みたいなところ。さてそれをObjCで実装する。まず、ベタに書くとこんな感じ。

// ObjC
@interface NSArray (Map)
- (NSArray *)mapUsingBlock:(void (^)(id))block;
@end
@implementation NSArray (Map)
- (NSArray *)mapUsingBlock:(void (^)(id))block {
  NSMutableArray *newArray = [NSMutableArray array];
  for (id item in self) {
    id obj = block(item);
    [newArray addObject:obj];
  }
  return array;
}
@end

これはこれでなんかスッキリしてていいんじゃねって気がしてきた。でもせっかくなので、enumerateObjectsUsingBlock:を使って書き直してみる。

// ObjC
- (NSArray *)mapUsingBlock:(void (^)(id))block {
  NSMutableArray *newArray = [NSMutableArray array];
  [self enumerateObjectsUsingBlock:^(id item,NSUInteger idx,BOOL *stop){
    id obj = block(item);
    [newArray addObject:obj];
  }];
  return array;
}

ちょこっと説明する。ブロックは、RubyやPerlのそれと同じく、生成されたコンテキストにある変数を参照できる。上のコードで言うとnewArrayはブロックの中じゃなくて外で宣言されてるけど、ブロックはその時点でのコンテキストを持ってるので、ブロックを生成したスコープで見えるものはブロック内でも同じように見える。

で、使う側ではこんな風に書けるようになる。

// ObjC
NSArray *array = [NSArray arrayWithObjects:@"ひたぎ",@"真宵",@"駿河",@"撫子",@"翼",nil];
NSArray *newArray = [array mapUsingBlock:^(id item){
  return [NSString stringWithFormat:@"%@ー!俺だーッ!結婚してくれー!",item];
}];

おお。それっぽいそれっぽい。

__block

ちょうどタイミング良く昨日弾さんとこで同じような話をしてたんだけど、上の例はしれっとブロックの外の変数に破壊的操作をしてるけど、例えばこういうのはできない。

// C or ObjC
typedef int (^bint)(void);

bint make_incr() {
  int n  = 0;
  bint f = ^int(void){ return n++; };
  return f;
}

ObjCのBlocksの実装だと、何も指定してないとブロックの外の変数は「見える」だけで「変更できない(ブロックの中から再代入できない)」。でも__block修飾子を付けて変数を宣言すると、ブロックの中から変数を変更できるようになる。それから、そのままfを返しちゃうと、関数のスコープ抜けたときにfが消えちゃうので呼び出し側で使えないところに注意する必要がある(試しにやってみたら、実際には最初の一回だけ実行できたものの、二回目を実行しようとしたところでbus errorになった。ブロックが解放されちゃってるはず)。nをブロック内で変更できて、スコープ外でも使えるブロックを返す関数を書くなら、

// C or ObjC
typedef int (^bint)(void);

bint make_incr() {
  __block int n = 0;
  bint f = ^int(void){ return n++; };
  return Block_copy(f);
}

こんな感じになる。ちなみに、このコードはObjCで書かれてる部分が無いのでMacOSX 10.6だったらCのコードとしてもObjCのコードとしてもコンパイルできるけど、ObjCの場合は<Foundation/Foundation.h>を、Cの場合は<Block.h>をimoport(include)する必要がある。あと、Block_copyして関数のスコープ外で使えるようにしたときは、当然呼び出し側で責任持ってBlock_releaseしてブロックを開放してやる必要がある。

// Cの場合
BLOCK_TYPE block = ^{ ... };
Block_copy(block);
...
Block_release(block);

// ObjCの場合
// Cと同じやりかたでも問題ないし、
// copyメソッドがBlock_copyと、releaseメソッドがBlock_releaseと対応してるので
// そっちも使える
BLOCK_TYPE block = [^{ ... } copy];
...
[block release];

// あとautoreleaseも使える
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BLOCK_TYPE block = [[^{ ... } copy] autorelease];
...
[pool release]; // ここでblockも開放される

ちなみになんでNSMutableArrayの方は破壊的な操作をできるのかというと、多分変数の操作じゃなくてオブジェクトへのメッセージ送信をしてるだけだから。[newArray count]とかやるのと意味的には同じなのでできるってことじゃないのかな。で、newArray = ... とか変数自体をいじろうと怒られるはず。

次回予告

Grand Central Dispatchで遊んでみた編。多分。

書こう書こうと思ってたけど忘れてたのを、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で使えるのかい?ってのは聞かないでくだしあ。泣きたい。

↑このページのトップヘ