Objective-Cのブロック(Blocks)に関するメモ

関数が第一級じゃない言語とかやってらんないわー、これだから静的言語は云々、というLL厨に対抗してか、最近は静的な言語でも第一級関数を取り入れようとあの手この手でグロい構文を導入してきています。Objective-Cもその例に違わずブロック(Blocks)という無名関数っぽい文法をiOS4.0あたりで取り入れたそうです。

とりあえず使ってみる

#import <Foundation/Foundation.h>

int main()
{
  typedef int(^calc_t)(int, int);
  
  NSArray *calcs = [NSArray arrayWithObjects:
    ^(int x, int y) { return x + y; },
    ^(int x, int y) { return x - y; },
    ^(int x, int y) { return x * y; },
    ^(int x, int y) { return x / y; },
    nil
  ];
  
  for (calc_t calc in calcs)
    printf("%d\n", calc(3, 3));
  
  return 0;
}
$ gcc main.m -framework Foundation
$ ./a.out 
6
0
9
1

宣言の仕方はポインタの*^にした感じですね。引数にラベルを付ける意味が皆無だからか、Objective-CではなくCっぽいスタイルです。

クロージャは出来るの…か?

__blockを使うとブロック外の変数を参照できるようになるようですが、果たして…。

#import <Foundation/Foundation.h>

typedef int(^counter_t)();

counter_t generateCounter()
{
  __block int count = 0;
  return ^(){ return count++; };
}

int main()
{
  counter_t counter = generateCounter();
  printf("%d %d %d\n", counter(), counter(), counter());
  return 0;
}
$ gcc main.m -framework Foundation
main.m:8:10: error: returning block that lives on the local stack
  return ^(){ return count++; };
         ^~~~~~~~~~~~~~~~~~~~~~
1 error generated.

う〜む。「ブロックはスタックに積まれるから、関数から出たときに破棄されるので、この書き方は出来ませんよ。」ということでしょうか。

ということを鑑みてか、ブロックをコピーできるBlock_copyという何とも言えない命名センスの関数があるようです。これを使うとスタック上のブロックをヒープへ移動してくれるようなのです。が、逆に言えばヒープの解放Block_releaseを自力で呼ばなければなりません。

#import <Foundation/Foundation.h>

typedef int(^counter_t)();

counter_t generateCounter()
{
  __block int count = 0;
  return Block_copy(^(){ return count++; });
}

int main()
{
  counter_t counter = generateCounter();
  printf("%d %d %d\n", counter(), counter(), counter());
  Block_release(counter);
  return 0;
}
$ gcc main.m -framework Foundation
$ ./a.out 
0 1 2

ARCが絡むと…

今やデフォルトでmainの中に@autoreleasepoolが自動生成される時代になり*1、ARCに関する理解はiOS開発者に必須のものとなっていますが、そんな前置きは置いといて本題ですが、とりあえず「blocksは外の変数をstrongで参照する」ということを抑えておけば良さそうです。__block __weak Foo* _self = self;のようにすればweakで参照できるようです*2

*1:autoreleaseとは何だったのか

*2:weak参照の変数をstrongでキャプチャすることになる、という解釈だと思う