デリゲートについて
デリゲート(delegate, 委譲)とは、あるオブジェクトがプログラム中でイベントに遭遇した際、それに代わって、または連携して処理するオブジェクトのことです。関数のポインタやコールバック関数に近い機能で、クラスの動作をクラスを継承せずにカスタマイズするための仕組みです。ソースコードが無いライブラリーの拡張が容易に行えるメリットがあります。デリゲート先のオブジェクトはどんなオブジェクトでも良く、デリゲートしないときはnilを指定しても構いません。Objective-Cでは実行時解決を採用しているので、デリゲート先に指定したオブジェクトが必要なメソッド(デリゲートメソッド)を持っていなくてもコンパイルエラーにはならず何も実行されないだけとなります。あるオブジェクトはひとつのデリゲートしか持てないが、複数のオブジェクトが一つのオブジェクトをそれぞれのデリゲートとして指定することはできます。
リゲートの動きを確認
delegateオブジェクトがメッセージを扱えるかを、respondsToSelector で確認し実行します。
@interface Foo : NSObject { id delegate; } -(void) sendMessage; @property (assign) id delegate; @end @implementation Foo @synthesize delegate; -(void) sendMessage { if (delegate == nil) { NSLog(@"hello, Foo"); } else if ([delegate respondsToSelector:@selector(sendMessage)]) { [delegate sendMessage]; } } @end @interface Bar : NSObject -(void) sendMessage; @end @implementation Bar -(void) sendMessage { NSLog(@"hello, Bar"); } @end #import <UIKit/UIKit.h> #import <Foundation/Foundation.h> int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Foo *f1 = [[[Foo alloc] init] autorelease]; Foo *f2 = [[[Foo alloc] init] autorelease]; // 以下の2行で委譲している Bar *b = [[[Bar alloc] init] autorelease]; f2.delegate = b; [f1 sendMessage]; [f2 sendMessage]; [pool release]; return 0; }
Foo のインスタンス f1は、sendMessageメッセージを送った場合は自身のメソッドが呼ばれるため "hello, Foo"が出力されます。しかし、Foo のインスタンス f2 は Barのインスタンスb に委譲されるため、"hello, Bar" が出力されます。
デリゲートを実装
プロトコルを利用して、プロトコルに適合するオブジェクトしかデリゲートできないように作ってみます(一例)。DoSomethingクラスで、doSomthingMethodメソッドが呼ばれた際に 利用側のcallThisMethodというメソッドに処理を委譲したい場合:
DoSomethingクラスの定義とプロトコルの作成
#import <Foundation/Foundation.h> // プロトコルの作成 @protocol DoSomethingDelegate <NSObject> // 利用側で実装しなければいけないデリゲートメソッド -(void) callThisMethod; @end // プロトコルを採用したid型のインスタンス変数を作成 @interface DoSomething : NSObject { id <DoSomethingDelegate> delegate; } // プロパティーの作成 @property (retain, nonatomic) id <DoSomethingDelegate> delegate; // メソッド -(void) doSomthingMethod; @end
DoSomethingクラスの実装とデリゲート
#import "DoSomething.h" @implementation DoSomething @synthesize delegate; -(void)doSomthingMethod { // 利用側でデリゲートメソッドが実装されているかチェックをし呼び出す if ([delegate respondsToSelector:@selector(callThisMethod)]) { [delegate callThisMethod]; } } @end
DoSomethingクラスの利用
#import <UIKit/UIKit.h> #import "DoSomething.h" @interface ViewController : UIViewController <DoSomethingDelegate> - (void) callThisMethod; @end
デリゲートメソッドの実装
#import "ViewController.h" @implementation ViewController ... - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; DoSomething *dodo = [[DoSomething alloc] init]; // デリゲート dodo.delegate = self; [dodo doSomthingMethod]; } #pragma mark - delegate // デリゲートメソッドの実装 - (void) callThisMethod { NSLog(@"通知メッソドが呼ばれました"); }
デリゲートが動作しない場合
デリゲートがうまく動作しない原因として、デリゲートメソッド名のスペルの間違いが多い。デリゲートメソッド名が間違っていても、メソッドが無いと判断され何も処理されないだけとなってしまいます。きちんとメソッドが呼ばれているか、デバッガでブレークポイントを設定して確認してみると良いでしょう。
また、デリゲート(インスタンス)を指定し忘れた場合と別のクラスのインスタンスへ指定した場合も同様にうまくデリゲートが動作しないことでしょう。これもブレークポイントを設けて動作確認することで容易に確認できます。