はじめに
Swiftを勉強し始めた人が一番初めにつまづく場所、それがデリゲート(delegate)だと思います。今Swiftをバリバリ書いてる人も、はじめはdelegateを調べることにかなりの時間を使ったのではないでしょうか。かくいう私も、delegateで詰まったりdelegate周りでいろいろ職場でやらかした人の一人です。そのような人を一人でも少なくするために、この記事を書きます。
※タイトルは誇張表現です(笑)
ですが、この記事を見た後は他の記事を見る必要がないレベルまでわかりやすく書くつもりです。
プロトコル(protocol)
delegateを覚える前に前提として、Swiftについて知っておいてほしいことがあります。正直これさえ読んでる方に伝われば、この記事の意義は果たせたかなとさえ思えます笑
Swift は プロトコル指向言語
Swiftはオブジェクト指向的に書くことができます。クラスを定義したり、そのクラスを継承した子クラスを作ったり、クラスのインスタンスを作ってその中のメソッドを呼び出したり。。。
ですが、AppleはSwiftをオブジェクト指向言語とは言っていません。
Appleはこう言っています。「Swiftは、プロトコル指向言語である」と。
プロトコルの役割
他の言語を学習済みの方がイメージを掴みやすくなるために書くと、プロトコル(protocol)は抽象クラスやインターフェースと呼ばれるものと同じです。以下で、コードを示しながら解説します。
はじめに、Animalというプロトコルを定義します。
protocol Animal {
func cry()
}
このプロトコルの意味としては、「Animalプロトコルの中身には必ずcryメソッドが実装してあります」と言っていると考えてください。
これとは別に、Dogクラスがあったとします。中身は何も書いていません。
class Dog {
}
このコードは問題なくコンパイルできますし実行もできます。(中身のないクラスを定義しただけなのでエラーになりようもありませんが。。。笑)
しかし、よく考えてください。犬は動物ですよね?ですので、DogクラスはAnimalプロトコルに従っているとして再度宣言してみます。
class Dog: Animal {
}
このコードはコンパイルするとエラーとなります。なぜなら、Animalプロトコルに従っているクラスはcryメソッドを実装しなければならないのに、そのcryメソッドが無いからです。ですので、Dogクラス内にcryメソッドを実装し直します。
class Dog: Animal {
func cry() {
print("wan!")
}
}
こうすればエラーはなくなります。つまりプロトコルとは、
「〇〇クラスには△△メソッドが実装されている、というのをわかりやすくする」
という役割を持っていることがわかります。
デリゲート(delegate)
なぜ上のような、プロトコルの説明から始めたかと言うと、delegateの使用にはプロトコルの宣言が必須だからです。以下で、delegateの説明に入ります。
デリゲートの役割
以下に、delegateを使ったコードのサンプルを示します。
var delegate: Animal!
delegate = Dog()
delegate.cry()
Swiftでは変数名の後ろに:をつけて、その変数のクラスを宣言することができます。が、実はクラスだけではなくプロトコルで宣言することもできます。上のコードでは、Animalプロトコルに準拠した変数delegateを宣言しています。そして宣言後にDogクラスのインスタンスを作り、delegateに代入しています。これができるのは、DogクラスはAnimalプロトコルに準拠していると宣言しているからですね。最後に、Animalに準拠したクラスではcryメソッドが実装されているはずですので、変数delegateからcryを呼ぶことができています。
したがって、上のコードをコンパイルして実行すると標準出力には以下のように出力されます。
wan!
いかがでしたか?プロトコルがわかった上で上のコードを読むと、だいぶ読みやすいのではないかと思います。まとめると、Main.swiftの中にはcryメソッドは実装されていませんが、delegateには実装されていることがわかっているので呼ぶことができる、というのがミソですね。
ちなみに、Catクラスを宣言すれば、出力結果を変えることもできます。
class Cat: Animal {
func cry() {
print("nya-")
}
}
var delegate: Animal!
delegate = Dog()
delegate.cry()
delegate = Cat()
delegate.cry()
結果:↓
wan!
nya-!
したがって、delegateとは
プロトコルの指定のみで、変数(インスタンス等)を持つことができる機能、及び
変数内に存在するメソッドがわかっていれば、外からそのメソッドを呼ぶことができる機能
のことを指しています。
よく使われている、delegateの例
delegateの全体像を掴んでいただいたところで、iOSのプログラミングをする際によく出てくるdelegateの例を紹介し、理解を深めて貰えればと思います。
delegateを頻繁に使う例として最もよく出てくる例が、UITableViewです。これは〇〇一覧画面等でよく出てくる、スクロールしてセルを見る画面のことです。
これらのようなUITableViewを使うサンプルコードを、以下に示します。
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView!
override func viewDidLoad() {
tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "SampleCell", bundle: nil), forCellReuseIdentifier: "cell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! SampleCell
return cell
}
}
大雑把にUITableViewを作るときのコードを書きました。
Appleのドキュメントを見ればわかりますが、前提としてUITableViewクラスにはもともとdelegateとdataSourceという2つの変数が宣言されています。そして、UITableViewのクラスはインスタンスが作られたとき、dataSourceに実装されている、
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
が呼び出され、tableViewのセルの数が決定します。
そして、画面内に表示されるべきセルがあるときはdataSourceに実装されている
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
が呼び出され、表示されるセルの中身が決定します。
これより以下で、実際にdelegateが使われている部分を解説します。勘のいい方はすでにdelegateがどの部分で使用されているかお気づきだと思いますが、delegateが使われているのは以下の部分ですね。
tableView.delegate = self
tableView.dataSource = self
このように、delegateにself(自分自身)を代入することもできます。ちなみに、UITableViewのdelegateはUITableViewDelegteプロトコル、dataSourceはUITableViewDataSourceプロトコルに従っている必要がありますので、ViewControllerの宣言部分でそれらに準拠していることを明記しています。(これがないとエラーになります。)
こうすることで、
dataSourceのメソッドが呼ばれるはず
↓
dataSourceを自分自身に設定する
↓
自分自身のメソッドが呼ばれるようになる
ということになり、呼ばれるメソッドの中身を好きに実装することができるようになります。iOSにはこのように、AppleがUIKitによってメソッドを呼ぶところまでは実装してあるが、メソッドの中身自体はdelegateとして別のインスタンスに委ねられていることが頻繁にあります。そういった作りがdelegateということですね。(上の例では、セルの数やセルの中身を決める処理自体をdataSourceに任せています。) 余談ですがこのように、処理を呼ぶけど処理の中身自体は外部に委譲してしまっていることから、delegate(委任、委譲する)と呼ばれています。
まとめ
Swiftの中心的思想であるプロトコルの解説から入り、delegateとは何かの説明とその使用されている例の紹介をしました。読んでいる方がdelegateのメソッドを見つけた時に、これはdelegateを使っているんだなと思いつけるようになっていれば幸いです。基本的に、メソッドが実装されているのに呼んでいる場所が見つかりにくかったら、それはdelegateだと思います(暴論)。最初はいろいろなクラスを行き来したりして考えるのが大変かもしれませんが、慣れれば便利な機能だと思えるようになってくるので頑張ってください!!
#余談
実はこの後に私が改めて調べていて勉強になった、protocol extensionやoptional methodについても書こうと思っていたのですが、protocolとdelegateの説明で思いの外長くなってしまったので、別の記事にしようと思います。乞うご期待!!