Objective-Cのdelegateをblocksで書く | パークのソフトウエア開発者ブログ|ICT技術(Java・Android・iPhone・C・Ruby)なら株式会社パークにお任せください

パークのソフトウエア開発者ブログ|ICT技術(Java・Android・iPhone・C・Ruby)なら株式会社パークにお任せください

開発の解決方法や新しい手法の情報を、パークのエンジニアが提供します。パークのエンジニアが必要な場合は、ぜひお気軽にお問い合わせ下さい。 株式会社パーク:http://www.pa-rk.co.jp/


Objective-C(Cocoa、UIKit)の組み込みクラスでは、delegateというパターンが多用されています。
これは、イベントのコールバックメソッドを呼び出し元のクラスに記述し、呼び出し先のフィールドに呼び出し元クラスへの参照を登録するというものです。
(オブジェクト指向でいう「委譲」と構造的には同じものを指していますが、ここではイベントのコールバックに限ったものとしておきます。)

例えばUIActionSheetやUIAlertViewのボタンクリックのコールバック、UITableViewのセル選択時のコールバックなどは、delegateを用います。
また、遷移先の画面で行った何らかの操作を、遷移元に反映させる場合にも使えます。

しかし、例えば一つの画面で複数のUIAlertViewを出し、それぞれボタンクリック時に違う処理を行いたい場合、コールバックメソッド内でtagによる条件分岐などを行う必要が生じ、コードが煩雑になり、処理の流れが見えにくくなります。
そこで、iOS4.0からはblocksと呼ばれる関数オブジェクトがサポートされていますので、これを使ってシンプルにUIAlertViewのコールバックを記述する方法を紹介します。

ここでは、以下のように使えるUIAlertViewのサブクラス、MyAlertViewを作ります。

・・・・・・
MyAlertView* alert=[[MyAlertView alloc]
initWithTitle:@"title"
message:@"message"
delegate:nil
cancelButtonTitle:@"cancel"
otherButtonTitles:@"ok", nil];

alert.didClick=^(NSInteger buttonIndex){
//some processes
};

[alert show];
・・・・・・

今回は押されたボタンのインデックスのみ受け取ることにしますが、もしTextInputつきの場合もサポートし、入力された内容も使いたい場合、ブロックの引数を変更する必要があります。

MyAlertView.hは例えば以下のようになるでしょう。

#import <Foundation/Foundation.h>

@interface MyAlertView : UIAlertView

@property (copy) void (^didClick)(NSInteger);

@end

ブロックリテラルはスタックに領域が確保されるので、呼び出し元で上記exampleのようにブロックを渡しても、そのメソッドが終了したらブロックリテラルも消えてしまいます。
したがって、コールバックのためのblocks propertyを宣言する場合、copy属性を指定する必要があります。

MyAlertView.mでは、UIAlertViewのdismissWithClickedButtonIndexをオーバーライドして、ボタンクリック時のコールバックを実行します。

#import "MyAlertView.h"

@implementation MyAlertView

- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
{
if(self.didClick){
self.didClick(buttonIndex);
}
[super dismissWithClickedButtonIndex:buttonIndex animated:animated];
}

@end


これで、blocksでコールバックを処理するMyAlertViewができました。

ただし、呼び出し側が以下のような構造のときは注意が必要です。

ブロックリテラル内でインスタンス変数にアクセスすると、それをメンバーに持つオブジェクトのretainCountがインクリメントされるという性質があります。
従って、MyClass -> myAlert -> ブロック -> MyClass というリテインサイクルが発生し、メモリリークの原因となります。
こういった場合、Xcodeはちゃんと警告を出してくれますが、blocksを使う際は保持について特に気をつける必要があります。
このケースでは、従来通りdelegateを使えば解決できます。

このように、blocksを使うとよりシンプルにコールバックを記述できます。
同じ方法でUIActionSheetやUIViewController同士の値の受け渡しも行えますので、ぜひ活用してみてください。