1.4 UIViewController2 ModalViewController (storyboard) 知識的なこと
内容
先にModalで行われる処理の概要を説明して後で、作業を各個撃破する方針でまとめ直しました。
長くなったので、作業分は次の記事にします。
- 知識的なこと
- Modal概要
- UIViewControllerとModalViewControllerの親子関係について
- Modalを削除する処理の責任を持つのはUIViewControllerのはずなのだが
- delegateパターン
- Javaによるサンプル
- delegateパターンとインタフェースの実装の違いについて
- Objective-Cにおけるdelegateパターン
知識的なこと
Modal概要
UIViewControllerは他のViewControllerと連携して新しいViewControllerを表示するという役割を持っています。
連携のやり方にはModal, Navigation Controller, TabBarControllerなどの方法がありますが、この章ではModalを用いた方法を解説します。
Modalを扱うModal View Controllerは「現在のViewControllerで行っている操作を一時中断して新しいViewControllerを表示する」というケースで使うようなことを想定しています。公式ドキュメントには以下のようなケースで使うことを想定しています。
- ユーザから直ちに情報を収集するため
- 何らかのコンテンツを一時的に表示するため
- 作業モードを一時的に変更するため
- デバイスの向きに応じて代替のインターフェイスを実装するため
- 特殊なアニメーショントランジションを使用する(またはトランジションなしの)新しいビュー 階層を表示するため
UIViewControllerとModalViewControllerの親子関係について
UIViewControllerは1つのModalViewを表示することができます。UIViewControllerがModalViewを表示したとき、 UIViewControllerが表示する側の親となり、ModalViewControllerが表示される側の子となる親子関係が出来ます。
表示している側のUIViewControllerは、property:presentedViewControllerに表示対象のModalViewControllerへの参照を持ちます。また、表示されている側のModalViewControllerは、propterty:presentingViewControllerに親のUIViewControllerへの参照を持つことが出来ます。
(出来ますにしているのは、modalViewController propertyはiOS6からdeplecatedなので使用しないほうがいいらしいからです。)
Modalを削除する処理の責任を持つのはUIViewControllerのはずなのだが
「あるクラスのインスタンスを生成したら、生成したクラスが責任を持って処理を行う」原則に則れば、本来Modalを閉じる処理の責務を持つのは、ModalViewControllerを生成して呼び出したUIViewControllerのほうです。
しかし、実際にモーダルを表示して何か操作を行うとき、使った側であるUIViewControllerがその操作の完了を検知できないため、処理に不都合がある場合があります。
たとえば、Modal内にあるボタンをタップしたらModalを閉じる、という場合を考えます。
この場合、Modalを扱うModalViewControllerから、ボタンがタップされたことをUIViewControllerに通知する必要があります。
一番簡単なのは、ModalViewControllerにUIViewControllerへの参照をプロパティとして渡しておいて、ボタンがタップされた時に呼び出されるメソッド内でUIViewControllerのインスタンスメソッドを呼び出すことです。
しかし、この場合、
- 結局、ModalViewController内にModalを書かれる処理が書かれる(=Modal側が自分で閉じている)
- ModalViewControllerは呼び出したいUIViewControllerのインスタンスメソッドを知っている必要がある(依存性)
ことになり、オブジェクト指向的によろしくない感じです。
delegateパターン
Javaによるサンプル
このような場合は、delegateパターンを用いて解決します。delegateパターンはデザインパターンの一種でGtoFには含まれていませんが、ゲーム製作では非常によく使われるパターンのようです。
メソッドのインタフェースだけ宣言しておき、あるクラスはそのメソッドを実装します。このメソッドを利用する側は、そのクラスについて知る必要はなく、インタフェースに従うのみとすることでクラス間の依存を取り除くデザインパターン
の説明を呼んで、いわゆるインタフェースと文法以外何が違うのか、protocolとはいわゆるインタフェースに宣言したメソッドなのではないのか、みたいな感じで自分には分かりにくかったので、少し丁寧にまとめておきます。
よい資料がよくわからなかったので、http://en.wikipedia.org/wiki/Delegation_pattern#Java_examplesのJavaのサンプルを使いながら説明します。
Delegateパターンには、delegate(代表者、派遣団員)と呼ばれるメソッドの実態を持っているクラスと、delegateのクラスへの参照を持ち、ポインタを通じてdelegateのメソッドを呼び出すdelegation(代表団)と呼ばれるクラス、そして、delegate,delegationの両クラスが持っているメソッドを規定するインタフェース(ない場合もあります)を用います。
まず、インタフェースを使ってdelegate,delegationの両クラスが持っているべきメソッドを定義します。
interface I { void f(); void g(); }
delegateにあたるクラスはインタフェースを実装します。
class A implements I { public void f() { System.out.println("A: doing f()"); } public void g() { System.out.println("A: doing g()"); } } class B implements I { public void f() { System.out.println("B: doing f()"); } public void g() { System.out.println("B: doing g()"); } }
delegationもインタフェースを実装しますが、delegateクラスのインスタンスへのプロパティ(this.i)を持っていること、実装したメソッドでは、iのメソッドが呼び出されていることに注目します。
class C implements I { I i = null; // delegation public C(I i){ this.i = i; } public void f() { i.f(); } public void g() { i.g(); } // normal attributes public void to(I i) { this.i = i; } }
このように実装しておくと、呼び出し側のクラスでは、delegationのインスタンスがどのdelegateのインスタンスを指示しておくだけで、呼び出し側のメソッドはdelegateのクラスについて気にしなくていい(ように見える)のです。
public class Main { public static void main(String[] args) { C c = new C(new A()); c.f(); // output: A: doing f() c.g(); // output: A: doing g() c.to(new B()); c.f(); // output: B: doing f() c.g(); // output: B: doing g() } }
delegateパターンとインタフェースの実装の違いについて
ますます、"Modalを削除する処理の責任を持つのはUIViewControllerのはずなのだが"でやっていることをインタフェース使ってやるのと何が違うんですか?という気がしてきたのですが、書いているうちに気づいたことをメモします。
まとまったら編集し直すと思います。
インタフェースを使ってそのまま実装する場合も、インタフェースを実装しているクラスAを用いる時に個別のメソッドの中身を知らなくても、 とりあえずそのメソッドがあるから呼び出していいのだろう、と呼び出し側でクラスAの中身を知らなくても使用できるところは同一です。しかし、あくまで、これらのクラスの代表となっているものとして呼び出し側のメソッドを書くときに意識する対象はインタフェースです。
クラスAをdelegateとしてDelegateパターンを用いる場合は、インタフェースを使うことは確かなのですが、呼び出し側のメソッドが意識するのは、クラスAが実装しているインタフェースではなくて、クラスAのインスタンスの参照を持っているdelegationのメソッドなのかな、と思いました。
実装する時のこと考えると、インタフェースかそれに類する何かを定義することは考えられないですし、実際インタフェースの宣言と実装の変形みたいな気もします。
と、思っていたのですが、今日講義を聴いていて思ったのは、
メソッドが実行される先は、delegationのプロパティのメンバではなくて、delegationのプロパティのメンバの参照をたどっていった、delegateクラスのインスタンスが行う、ということが大事だからDelegateパターンなのかな
と思いました。よく分からないけれど。
Objective-Cにおけるdelegateパターン
実装は後で説明するので、簡単に概要を説明します。
脇道に寄ったので、あらためて話を戻すと、Objective-Cでdelegateパターンを使う場合は、親子関係のUIViewControllerとModalViewControllerにおいて、親側のUIViewControllerの責任でうまくModalを閉じたい、という場合でした。
この場合、実際のメソッドを実装するdelegate(派遣団員)はUIViewControllerにあたり、delegateへの参照を持つdelegation(代表団)はModalViewControllerの方です。
Javaの例と異なるのは、
- delegateが持つべきメソッドを規定するのはインタフェースではなく、プロトコルという仕組みを用いること
- プロトコルの働きはいわゆるインタフェースに近いが、delegateがプロトコルに書かれたメソッドを実装することをプロトコルに準拠すると呼ぶこと
- プロトコル名はdelegation名+Delegateという名前であり、同じdelegationに使用するdelegateのクラスすべてで同じであること
- delegationはdelegateクラスのインスタンスへの参照を持つのではなく、delegation名+Delegateオブジェクトへの参照を持つこと
- delegation名+Delegateオブジェクトへの参照は弱参照であること(違いではないですが特徴として)
です。個人的に、結局ModalViewControllerでボタンタップのハンドラを書くときには、UIViewControllerのコードを確認しておかなきゃいけないのね、プログラマが。
という気がしましたが、そういうお作法らしいので、この辺で止めておきます。
追記
gitterより引用。
delegateしたいのは、単に画面閉じるだけなら必要ないんだけど、画面閉じるタイミングに併せて、親の画面がデータのリロードするとか、他の事もしたくなった時に必要になります。
メソッド一覧眺めるだけでも UIViewControllerがどんなものか、何となくわかるかも