Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 260 - プロトコールとデリゲーション ProtocolsとDelegation

2019年5月30日;Swift5を利用して、Appleの推奨するプロトコール指向プログラミング(POP)の解説に変更
2016年10月17日:Swift3向けにイメージを変更*1

この記事を書いていた頃は、オブジェクト指向プログラミング(OOP)の理解に苦しんでいました。WWDC2015で、protocolextensionを使った、protocol-oriented programming(POP)、プロトコール指向プログラミングが紹介され、OOPの理解をしたところだったので、POPの考え方も理解できました。OOPはクラスの拡張にしか使えませんが、POPで実装していけば、クラスだけに囚われず、あらゆる型の拡張に使えるから便利です。そして、コードの可読性もあがります。今日からPOPにプログラミングスタイルを変えていきましょう。以下にOOPの実装とPOPの実装法を並べて説明します。

FaceView(V)とHappinessViewController(C)を繋げていくためにプロトコールとデリゲーションの復習をします。

プロトコールの復習。これで何度目になるだろう?

復習をすればするほど理解は深まっていく実感はありますが、ペースが非常に遅いんで情けないです(T_T) このブログに読者が5人*2いるけど、「何やってんだ。おせーな。つきあってらんねーよ」という言葉が聞こえてきそうです(^^;)

今までのプロトコールとデリゲーションに関する記事を並べてみると...

まだまだデリゲーションを完全に自分のモノにしたとは言いがたいのですが、今回ポール先生の講義を聴いて、デリゲーションの実装の仕方は理解できたと思います。

Viewクラス(UIViewクラス)とControllerクラス(UIViewController)の関係性を使ってプログラムする例を考えてみたら理解しやすかったです。

OOPによる プロトコール・デリゲーション実装法

UIViewクラスのオブジェクト(インスタンス)はジェネリック(一般的)な存在なので表示機能は備わっていても、何を表示するか具体的なコンテンツを知る必要はありません。そのため、コンテンツの内容に関して他のオブジェクトに任せる必要があります。この任せることをデリゲーション(委譲)と呼びます。ちょっと分かりにくいのですが、処理を任されたオブジェクトのことをデリゲートと呼びます。すなわちUIViewControllerクラスのオブジェクトがデリゲートということです。日本語が妙なんですが気にしないで、デリゲーションの説明をします。

f:id:yataiblue:20161017172804j:plain

2つのクラスをカスタムに

  1. AViewクラスがAControllerクラスに任せたい(デリゲーション)処理をデリゲート・プロトコールとして用意します。ViewクラスにあるcontrolValueを外部のオブジェクトで操作してもらいたい(委任)のですが、この値を操作する関数「getControlValue(sender:)」をプロトコールとしてパブリックにオープンにします。
  2. デリゲート・プロトコールとして、デリゲート・プロパティをAViewクラス内で保持します。初期化ステップが発生するまで変更が加わらないようにするためにオプショナル設定にします。また循環参照を避けるためにweakにしてメモリーリークを回避します。
  3. デリゲート・プロパティにAViewである自分自身(self)を引数として与えることで、controlValueの具体的な値を取り出してきます。ここを理解できるとプロトコールとデリゲーションの関係が理解できたと言えるでしょう。基本的にdelegateというプロパティは、AControllerクラス内で自分自身である(AController)を指定しているため、このdelegateはAControllerクラスで記述している「getControlValue(sender:)」メソッドのみを持つ「AViewDelegateData型」になっているんです。ということで「delegate?.getControlValue(sender: self)」 でAControllerで記述されているメソッドを利用して具体的なInt型の値を取り出すことができます。
  4. 処理を委任(デリーゲート)されたAControllerクラスに先ほどのプロトコールを準拠させます。
  5. 先ほど説明したAController自身をAViewクラスのプロパティ(delegate)に指定することで、delegateは「AViewDelegateData型」になります。このステップは、コードを書いて実装する場合に必ず必要ですが、Xcode8を使って設定する方法もいくつかあります。次のリンクに説明があります。Swiftで遊ぼう! - 318 - My Picker Project : SingleComponentPicker Xcodeでデリゲーション - Swiftで遊ぼう! on Hatena
  6. プロトコールはブループリントなので、ここで具体的な処理をコードします。

以上! 異なるクラスをプロトコールを使って繋げることができます。

重要iOS API利用時は、上記のステップ(1-3)を必要としません。例えば、UIViewクラスやサブクラスなど、ジェネリックなクラスは、表示機能のみ受け持ち、コンテンツの内容と表示に関わる具体的な制御をデリゲーション(委譲)させるため必要なプロトコールを既に用意しています。「DataSource」と「Delegate」という2つのプロトコールがそれです。それぞれ同じデリゲートオブジェクトを指していますが、メソッドが異なります。「DataSource」はコンテンツに関わるもの(表示内容や項目数など)で、「Delegate」は表示に関わるもの(表示位置や配置)です。必ず2つのプロトコールを準拠させて、オプショナル以外の必須メソッドを記述する必要があります。

POPによる プロトコール・エクステンション実装法

UIViewクラスはジェネリックな存在ということに変わりはありません。表示機能しか備わっていないので、具体的なコンテンツは、UIViewConrollerにデリゲーションする必要があるので、そのための機能をプロトコールとして用意します。このステップは、OOPと同じです。

f:id:yataiblue:20190530131923j:plain

  1. AViewクラスがAControllerクラスに任せたい(デリゲーション)処理をデリゲート・プロトコールとして用意するところは、OOPと全く同じです。
  2. プロトコールの変数プロパティをUIViewに持たせるところもOOPと同じです。そして、このプロパティ型の変数は内部にAViewを保持するので循環参照が生じます。メモリーリックを回避するためにweakにします。
  3. AViewである自分自身(self)を引数として与えることで、controlValueの具体的な値を取り出す循環参照になっています。
  4. POPでここが大きくことなります。AControllerクラスはプロトコールに準拠する必要が無いので、クラス内部で具体的な実装コードを書く必要がありません。コードをシンプルにできて間違えにくくなります。
  5. このステップはOOPと同様です。プロトコール型のAVIewクラスをコントローラが 持って、プロトコール型のdelegateプロパティに自分自身を指定するため、当然、循環参照なのでweak指定です。
  6. AControlerにプロトコール型を準拠させるためにエクステンションを使って拡張させる訳です。

さあ、OOP、POP、どちらでも問題無く理解できましたよね。

*1:2015年11月30日:ここの内容はデリゲーションの基本です。完全に理解していないとiOS開発はできません。分からなければ質問して下さい、2015年5月27日追記:説明の補完、2015年5月11日追記:プロトコールとデリゲーションの理解は、iOS API利用のために必須です。「Swiftで遊ぼう! - 301 - Scroll Viewのデリゲート・メソッド」で具体的な実装法をデモンストレーションしています

*2:この記事を書いた時の読者数です。今はちょっと増えてます。読者の皆さんありがとうございますm(_ _)m