読者です 読者をやめる 読者になる 読者になる

Kikuchy's Second Memory

つくる楽しさをもっと伝えたい。プログラムを書いていて、わからなかったこと・気付いた事を書き留めています。

Objective-C の delegate パターンがよくわからなかったので勉強してみた

最近、 mixi-inc/iOSTraining · GitHub をやっています。
元々は mixi の社内トレーニング用らしく、解説が所々飛んでいたりしてなかなか初見殺しな内容です。(訂正 2013/5/29:サンプルコードがあることを知らずに自分で一から書いていたため、やたら難しく感じていたようです)


Objective-C なんてほとんどやった事が無かったのですが、どうにかこうにか HomeWork 1.2 UIViewControllerとModalViewController · mixi-inc/iOSTraining Wiki · GitHub までたどり着いて、"delegate パターン"なるものに翻弄されています。
そこで少しだけ勉強してみました。


想定読者

  • C#, C++, Java のinterface か、抽象クラスについて理解している人

delegate パターンとは

例えばモーダルダイアログを表示するとしましょう。こで必要な役者は、

  • モーダルの呼び出し元クラス(ParentViewController)
  • モーダルとして表示されるクラス(ModalViewController)

になります。
ModalViewController はユーザーから何かしらの入力を受け取って、その値を PrarentViewController に渡す必要があります。

この二つのクラスの間で、「ParentViewController には ABC っていうメソッドを用意しておくから、 ModalViewController は値をそのメソッドの引数に入れて渡してね」という取り決めをしておくことになります。

その取り決めのため、実装すべきメソッドの名前や引数を決めたものが、 protocol というものです。


お気づきかと思います。早い話が、 protocol とは

  • C#, Java の interface
  • C++ の純粋仮想関数だけを持った抽象クラス

です(本当はちょっと違います)。

  1. 呼び出し元(ParetnViewController)が決まった protocol を実装しておいて、呼び出された方(ModalViewController)が特定のメソッドを叩く。
  2. そのために、呼び出された方(ModalViewController)は delegate という名前のメンバに、呼び出し元(ParetnViewController)を確保しておく。

これが delegate パターンだそうです。


イメージはこんな感じ。(初期化だとか、肝心でない所は全部省いているので、このコードをコピペしても動きません)
まず Objective-C

@protocol ParentViewControllerDelegate
-(void)getData: (NSInteger)data;
@end

@interface ModalViewController : UIViewController

@property (nonatomic, assign) id <ParentViewControllerDelegate> delegate;

-(void) passDataToParent;

@end

@implementation ModalViewController

@synthesize delegate;

-(void) passDataToParent {
    int data = (なんかこの辺で値取ってくる)
    [delegate getdata: data];
@end

@interface ParentViewController : UIViewCotroller <ParentViewControllerDelegate>
-(void)showModal;
-(void)getData: (NSInteger)data;
@end

@implementation ParentViewController
-(void)showModal {
    ModalViewController *modal = [[ModalViewController alloc] init];
    // modal を表示
    [self presentModalViewController:modal animated:YES];
}

-(void)getData: (NSInteger)data {
    (なんかこの辺で data を使って処理する)
    // ここでモーダルを閉じておくとお行儀が良いらしい
    [self dismissModalViewControllerAnimated: YES];
}
@end

うーん、 JavaScript みたく、

//var self = this;    // 渡す function の中で this が必要になったとき用
var modal = new ModalViewController();
this.presentModalViewController(modal, {animated: true, whenClose: function(data){
    (モーダルダイアログから渡されて来た data を使って処理)
}});

でなければ、

//var self = this;    // 渡す function の中で this が必要になったとき用
var modal = new ModalViewController();
model.addEventListener("close", function(data){
    (モーダルダイアログから渡されて来た data を使って処理)
});
this.presentModalViewController(modal, {animated: true});

こんな風に関数オブジェクト渡して処理できないものなんですかねぇ。
わざわざ一度しか使わないような protocol を宣言するとか、労力の無駄な気がするんです。ひしひしと。
あと、イベント駆動にした方が疎結合でエレガントな感じ。


JavaScript から Objective-C にコンパイルできるコンパイラとか無いんですかね?
Titanium Mobile 使っとけってことですかそうですか…







とか思ってたら、 presentModalViewController:animated のドキュメントを見ていたらこんな表記が。

Deprecated: Use presentViewController:animated:completion: instead.

非推奨メソッドじゃんこれ!!
代わりに、 presentViewController:animated:completion という新しいメソッドが推奨されているようです。
この新しいメソッドは completion: に Block (Objective-C のラムダ式みたいなもの)を入れられるらしい。
というと、今までのコードは、 JavaScript の例みたく、↓のように書き直せるってこと?


かと思ったら駄目でした。

@interface ModalViewController : UIViewController

@property (nonatomic, assign) int data;

-(void) pressOKButton;

@end

@implementation ModalViewController
@synthesize data;

-(void) pressOKButton {
    data = (なんかこの辺で値取ってくる)
    // モーダル閉じる
    [self dismissViewControllerAnimated:YES completion:NULL];
@end

@interface ParentViewController : UIViewCotroller
-(void)showModal;
@end

@implementation ParentViewController
-(void)showModal {
    ModalViewController *modal = [[ModalViewController alloc] init];
    // modal を表示
    [self presentViewController:modal animated:YES completion:^void{
        (modal.data を使って何かする)    // <- modal.data で参照したら、 ModalViewController の pressOKButton で入れた data の値ではなく、int 初期値の 0 が取れた
    }];
}
@end

一瞬「なんだ! もう delegate パターンいらないじゃん!」とか思ったら、値が渡せないんじゃ意味ないわな…
モーダルダイアログから値を取得しないなら、モーダル閉じた時の後処理だけ completion: の Block に書いておけば良いので、それで事足りるんでしょうけど…


結論: delegate パターンはまだ現役なようですので覚えて使いましょう。