もうすぐ春が来るので、新卒などswiftを新しく学習を始める方々の力になればと書きました。
前書き
swiftを書き始めると、よく遭遇する"delegete"、
初心者はまず『UITableViewを動かすために tableview.delegate = self
と記述しよう』として使うかもしれませんし、
少し学習を進めて『delegateを作成して、処理を移譲させてみましょう』という形で遭遇するかもしれません。
「あー、移譲ね…」と分かったような分からないような気持ちの方も少なくないのではないでしょうか。
そして「こうやって作れば動くよ」というドキュメントは多いのですが、
そもそも、どうして delegate なんていうモノを使わなければいけないのでしょうか。
今回はswift初学者が**「どうして delegate を使って実装するのか」**が理解できるよう、
またはdelegateも実装できるものの、どんな時に使うべきか悩むことの多い方が
いつ delegate を使って実装すべきかを判断できるように記事を書いていきます。
delegateとは「作戦の名前」
多くの初学者は『delegateって、プロトコルとセットのヤツ?』という曖昧な認識かもしれません。
swiftで一定の基本的を学び、ちょっとしたアプリなんかは作れるようになっても
delegateがしっくり理解できていない、という人が多いのには
delegateという概念が、どんなカテゴリに属するものなのか
はっきりと把握できていないことが原因だと思っています。
よく delegate を検索すると
**「別のクラスに処理を委譲したり通知を受ける仕組み」**のように出てきますが、
そういったことの前に、その言葉のカテゴリをハッキリさせます。
delegateとは、デリゲートパターンという「作戦の名前」、技名のようなものです。
アイシールド21を読んだことがある方は、アメフトの「作戦名」(スイープ、とかヘイルメリーパスとか)が一番近いかもしれません。
classやfunc はswiftのルールで決められた名称
これだけだとやはり何を言っているか分からないと思うので続けます。
例えば、
オブジェクトの設計図をclass、 メソッドをfunc と呼ぶのは
swiftの言語仕様であり、勝手に変えることもできません。protocolも同様です。
class Human {
let height: float
let weight: float
func speak() {
print("こんにちわ")
}
}
これを
klass Human { // class ではなく klass
let height: float
let weight: float
method speak() { // func ではなく method
print("こんにちわ")
}
}
こんな風に書いたら動きません。 class、 func、 protocol
、は
swiftのルールで決められたモノの名称です。
protocolも同様です。
protocol speakable {
func speack()
}
// ↑動く
protocoru speakable { // protocolではなく protocoru
func speack()
}
// ↑ 当然動かない
classは classと呼ばなければいけませんし、 func は func、 protocolはprotocolと呼ばなければいけません。
ルールで決まっている名称です。
delegate ではなく kamehameha(かめはめ波)でも動く
ではdelegateはどうでしょうか。
よく見かける delegate 実装のサンプルコードはこんな形でしょう。
まずこの中に delegateという文字がどこに何個あるか見てみましょう。
protocol AudioButonDelegate { // 「delegate」という文字 1つ目
func playSounds()
}
class AudioButton() {
delegate: AudioButonDelegate? // 「delegate」という文字 2つ、3つ目
func click() {
print("クリック!")
delegate.playSounds() // 「delegate」という文字 4つ目
}
}
class SomeViewController: UIViewController, AudioButonDelegate { // 「delegate」という文字 5つ目
var audioButton = AudioButton()
override func viewDidLoad() {
super.viewDidLoad()
audioButton.delegate = self // 「delegate」という文字 6つ目
}
func click() {
audioButton.click()
}
func playSounds() {
print("じゃーーーん")
}
}
6つの「delegate」という文字が出てきました。
ご丁寧に「delegate」と書いていますが、
別にこれ、delegate という名前にしなけれないけないというswiftのルールはありません。
「Kamehameha(かめはめ波)」 でも動きます。
protocol AudioButonKamehamaha { // 「kamehamaha」という文字 1つ目
func playSounds()
}
class AudioButton() {
kamehameha: AudioButonKamehamaha? // 「kamehamaha」という文字 2つ、3つ目
func click() {
print("クリック!")
kamehameha.playSounds() // 「kamehamaha」という文字 4つ目
}
}
class SomeViewController: UIViewController, AudioButonKamehamaha { // 「kamehamaha」という文字 5つ目
var audioButton = AudioButton()
override func viewDidLoad() {
super.viewDidLoad()
audioButton.kamehameha = self // 「kamehamaha」という文字 6つ目
}
func click() {
audioButton.click()
}
func playSounds() {
print("じゃーーーん")
}
}
これでも、何の問題もなく動きます。
作戦名 = パターン名
ではなぜ、これをみんな「delegate」と書くのでしょうか。
それはこの一連の流れ「SomeViewControllerが、AudioButtonを呼び出しているのだけど、AudioButtonが意図したタイミングでSomeViewControllerのplaySounds()を呼び出している。それらが循環参照にならないように"有名な方法"を使って実装している」、
この"有名な方法"を「delegateパターン」という作戦名、もといデザインパターンで呼んでいるのです。
デザインパターンとは、
プログラミング言語ごとではなく、さまざまなプログラミング言語に横断する
設計のベストプラクティスのことで、つまりは「有名な作戦名」です。
とくにGoFという4人が考案した23個のパターンが有名です。
デザインパターンは、プログラミング言語よりも、上の階層の概念、と言ってもいいかもしれません。
たとえば、有名なパターンの中の「オブジェクトを一意に保つプラクティスであるシングルトンパターン」は
javaで書く方法はこう、swiftで書く方法はこう、と言語横断的なプログラミングのプラクティス名です。
ヘイルメリーパスは、泥門デビルバッツでも王城ホワイトナイツでもできる、チーム固有の作戦ではなく、アメフトの横断的な作戦であるのと同じです。
そしてdelageteパターンもまた同様に、言語横断的なプラクティス名、つまり作戦名です。
この記事を参考にすると
オブジェクト指向プログラミングで、委譲は、送信側のオブジェクトの文脈で受信側のオブジェクトのメンバ(プロパティやメソッド)を評価することを指します。
ざっくり言えば
「呼び出される側が、呼び出し元に命令したくなったときに、(swiftではprotocolを介して)うまくやるベストプラクティス」が、
delegateパターン です。
プラクティスにわざわざ名称がついている理由は、
作戦名がつくくらいベストプラクティスが求められるという状況は
「プログラミングを書くと、みんなそこで悩む。でも解決方法を毎回1から説明すると複雑なので、
『あの方法で実装して』といえば伝わってほしいから」です。
なので、
「呼び出される側が、呼び出し元に命令したくなったときに、protocolを介してうまくやるときのプラクティスは
delegateパターンなので、 この肝になるprotocolは ○○Delegate って名前にしておけば、
このプログラムを読んだ人も、『delegateパターンだ』って理解してくれるだろう 」
という理由で、 ○○Delegate という名称になっています。
ここまでのまとめ
- delegateは、delegateパターンというデザインパターン
- デザインパターンとは、有名な設計のベストプラクティス(作戦名みたいなもの)
なんでdelegeteを使うの?
やっと本題です。
たとえば、sampleプログラムを実装して「ここではdelegateを使って実装しましょう」となっているものの
なぜ delegate を使って実装するのでしょうか。
delagateパターンの概念さえ知っていれば、なぜの理由を理解するまではあと少しです。
もう一度、delegateパターンの説明をすると
「呼び出される側が、呼び出し元に命令したくなったときに、うまくやるベストプラクティス」です。
なぜ使うかの、最後の1ピースとして
「プログラミングでは classが呼び出す方向性は一方方向にしなければいけない」
という基本的な考え方があります。
たとえば
class Some() {
init() {
let hoge = Hoge()
}
}
class Hoge {
init() {
let some = Some()
}
}
こんなプログラムがあったら、循環参照でメモリリークを起こしてしまう(つまりアプリが壊れてしまう)のは分かるでしょうか。
極端に言えばこんなことにならないように、プログラミングでは、アーキテクチャという構造のルールによって
classの呼び出しの方向性を決めています。
HumanがCarを使うのであって、CarがHumanを呼び出してはいけない、とか
ControllerはModelを介してDetaにアクセスする、とかです。
しかし、
「どうしてもこの方向を逆走したい瞬間」があったとき、
呼び出しの方向性自体はそのままで、呼び出し先が呼び出し元に、処理を依頼する方法としてよく使われるのが、delegateパターンです。
なので、なぜ使うかの理由は
「呼び出し先のclassが、呼び出し元のclassに、処理を依頼したくなったから」がほとんどです。
具体的な例で見ていきましょう。
// クリックされたら音を鳴らすボタンのカスタムクラスを作る。
// delegateを使いたい理由は以下。
// ①このプロジェクトではView層は音源データにアクセスできない。
// そのため、クリックされたら、音を鳴らせるだれかに、その処理を委譲したい
// ②このプロジェクトで音源データにアクセスできるのはViewControllerだとする
// そのため、AudioButtonは逆走して、ボタンを押されたら、自分を呼び出したVCに「音声再生して!」と依頼する必要がある
protocol AudioButonDelegate {
// AudioButon用のdelegateパターンを実現するためのprotocolなので、「Delegate」というprotocol名にしておく
func playSounds()
}
class AudioButton() :UIButton {
delegate: AudioButonDelegate? // 委譲先。
func click() {
print("クリック!")
delegate.playSounds() // 自分(Button)はできない音源再生を委譲する
}
}
// このclassは音源データを再生できるので、AudioButonDelegateの役割を担う(AudioButonDelegateを実装する)
class SomeViewController: UIViewController, AudioButonDelegate {
var audioButton = AudioButton()
override func viewDidLoad() {
super.viewDidLoad()
audioButton.delegate = self // AudioButtonのdelegate変数の中身に、自分をセットする。(委譲先は自分です!と設定する)
}
func click() {
// 呼び出し先の、audioButtonのclick()を実行する
audioButton.click()
}
// ↓このメソッドが、呼び出し先のAudioButtonの中の任意のタイミング、delegate.playSounds()において発火させられる
func playSounds() {
print("じゃーーーん")
// ここで音源データにアクセスして再生する
}
}
ここまでのまとめ
- delegateを使う理由はclassの呼び出しの方向を逆走した依頼をしたいから、が多い。
なぜswiftではdelegateが多いの?
swiftでdelegateをよく目にする理由は、
UIKit
などswiftの基本的なライブラリのパーツが、delegateパターンを利用して機能を提供しているからでしょう。
tableView.delegate = self
// ↑でdelegateを設定したので、tableViewの意図したタイミングで↓が発火する
// (意図したタイミング=tableViewがタップされたタイミング)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// tableViewのindexPathがタップされた
}
// 補足:ここでtableViewのdelegateプロパティは「UITableViewDelegate」で型指定されているため、
// これを呼び出すvc等は、プロトコル UITableViewDelegate を実装する必要がある。
あらためて見ると、いつも書いている tableView.delegate = self
の意味も
理解できるのではないでしょうか。
今後、カスタムviewのような汎用的なパーツを作成するとき、
PresenterからViewControllerを更新したいとき、などなど
呼び出し順を逆走したいと思ったら、delegateの利用を考えてみましょう。