ブロックをプロパティとして扱う

Androidの無名クラス、WindowsPhoneのラムダ(デリゲート)に慣れているプログラマはiOSに転向すると最初はそのような抽象がなくて絶望し、その後iOS4以降はBlocksが使えることに歓喜する。

Blockは基本的にはファンクションへのポインタ型で実装されており(Delphiを思い出すな)一時変数やパラメタとして使うのが普通だが、ならばプロパティとしても使えるだろうと以下のように定義する。

BBButtonProxy.h
@interface BBButtonProxy : NSObject
  @property (strong, nonatomic) NSString *label;
  @property (strong, nonatomic) void (^action)(); //※1
〜

〜
{
    //OKボタンが押下された場合の処理
    BBButtonProxy* yesItem = [[BBButtonProxy alloc] init];
    yesItem.label = @"yes";
    yesItem.action = ^
    {
        [self clearOrders];
        [self.tableView reloadData];
   };
}

定義自体に問題はなく、動作もはするが、ブロックの中でselfを直接又は間接的に参照した瞬間に「循環参照 (Self Capture)」に陥り、互いのメモリが解放されずに残ってしまう。また、プロパティactionの属性もstrongなので、BBButtonProxyはブロック自体を保持する。

なのでプロパティactionはコピー属性(copy)として、ブロック中で参照されるselfは__weakで修飾した別な変数に代入したのちに使用する。

@interface BBButtonProxy : NSObject
  @property (strong, nonatomic) NSString *label;
  @property (copy, nonatomic) void (^action)();※2

    //OKボタンが押下された場合の処理
    __weak HogeController* weakRef =  self;
    BBButtonProxy* yesItem = [[BBButtonProxy alloc] init];
    yesItem.label = @"yes";
    yesItem.action = ^
    {
        [weakRef clearOrders];
        [weakRef.tableView reloadData];
    };

※1:typedefを使って予め型を定義しておくこともできるが、このように書くことでブロック名がプロパティ名になりObjective-Cぽくて好きだ。

※2:このように書くのが一般的らしいのだが、copy属性はリファレンスによると「copyオブジェクトを挿入できるオブジェクト型のみ有効」とある。Blockを扱う場合、Block_copyが動作していると思うのだが、まだ検証していないので懐疑的だ。