Delegateまわりは正直よく理解できていませんでした。
ところが、先日ふとコード書いてたら、突然理解できました。
解説はすでにわかりやすいのがたくさんあるので簡単に書かせて頂いて、
オリジナリティ要素として「なぜ使うのか」部分にしぼって書きたいかなと思っています。
よくわかる解説記事
QiitaでDelegateというと、下記の記事2つあたりが有名どころっぽいです。
プロトコルとデリゲートのとても簡単なサンプルについて
【swift】イラストで分かる!具体的なDelegateの使い方。
正直サンプルコードとかは上の記事を見てもらうのがシンプルだし、
さらに噛み砕いて説明が欲しければ下の記事でいいのかなと。
Delageteとは
デザインパターンの一つです。
デザインパターンっていっても、GoFのデザインパターンの中にはないです。
(これが最初個人的に混乱しました)
あるクラスから、他のクラスに処理を「移譲」(Delegate)するパターンのことです。
初心者が自分から思いついて書こうとは思わないでしょうが、
UIKitの中にある部品(UITextFieldとか)がDelegateして使うのが推奨されていて、
初心者向けの解説でも容赦なく出てくるので、そこで初心者の詰まりポイントになりがちです。
サンプルコードは上で紹介した「とても簡単なサンプル」を見るのがいいと思います(怠慢)。
初心者向けの解説本とかだと、おまじない的な扱いになってるんじゃないかなーと。
ちなみに.NET Frameworkとかにもあるみたいです。
Swiftにおいては、protocolを使って、Delegateパターンを実現します。
Xcode上で見たいプロトコルをCommandを押しながらクリックすると、
Jump to Definitionという選択肢が出るので、それを見るとプロトコルの中身が見られます。
たとえばXMLのパースする際のDelegateですが、
public protocol XMLParserDelegate : NSObjectProtocol {
optional public func parserDidStartDocument(_ parser: XMLParser)
optional public func parserDidEndDocument(_ parser: XMLParser)
optional public func parser(_ parser: XMLParser, foundNotationDeclarationWithName name: String, publicID: String?, systemID: String?)
(略)
というprotocolがあらかじめFoundation中に書かれています。
よくある初心者向けの解説書だと、delegate先をselfとして記述するのではないかと思うのですが、
あれはViewControllerクラス自身をDelegate先に指定させて、関数の中身だけ自分で追記しているわけですね。
Delegateを初心者が理解できない理由
Delegateをちゃんと使えるというのが、ある意味初心者と中級者の間の壁なんじゃないかと思ってます。
初心者:入門本や動画を見ながら、写経しているレベル。
コードは基本全部ViewContorollerに書こうとする。
中級者:設計を考えて、より可読性・保守性の高いコードを書こうとする
iOSアプリの入門本を読むと、基本的には、
「◯◯という機能を実現するために、xxを使います」
と説明されると思います。
それに対して、Delegateというのはぶっちゃけ別になくても本来的には問題ありません。
あくまで「デザインパターン」なので、将棋の定石みたいに、
先人のプログラマたちの経験から生まれた、これ守るとええコード書けるよね、という共通認識でしかないのです。
まあただiOSアプリつくるさいに、ユーザーからの入力イベントの処理がないことってそうそうなく、
その手のイベント処理ではDelegateで書くのがお決まりなので、結果的にはDelegate使わないとアプリつくれなくはなってます。
なぜDelegateを使うのか?
イベントドリブンなプログラムを作る際は、Delegateにした方が筋がいいから、みたいです。
スマホアプリは、ユーザーからのイベント操作がメインになると思います。
その場合、
イベントを検知する処理 + イベント後にさせたい処理
という二段構えでコーディングすることになると思います。
もしこれを何の設計もなしに書くと、コードは複雑化します。
どう設計するのがきれいかというと、
- イベント発生を検知する処理
- イベント後にさせたい処理
でわけて書きたい。
1が発生したら、2に処理を委譲するような設計に……はっ、Delegate!
よくある例(2019/05/03追記)
Delegateが壁になるのは、開発者が意図してDelegateを使っているのではなくて、
UIKitとか、iOSアプリ開発のライブラリ使おうとしたときに、Delegateを使わざるを得なくなるからだと思います。
最初に紹介したDelegateのとても簡単なサンプルでもいいのですが、もう少しリアルな例で説明したいと思います。
UITableViewを使いたい(たとえばTwitterのUI)ときは、Delegateを使うのが必須になります。
たとえば下記のようなコードになるかと。
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var TableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
TableView.delegate = self
TableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
}
このViewControllerはTableViewを使うために、UITableViewDelegateとUITableViewDataSourceという2つのプロトコルに準拠しています。
どの入門書を読んでも、だいたいこんな感じで書くことになるのではないかと思います。
tableView(_ tableView, numberOfRowsInSection section)で1セクションの中の行数を指定しています。
returnする数が行数になるので、とりあえずそれだけわかっていればアプリは動かせると思うのですが、
なぜこういう風に書くのかという説明は、あまり入門書レベルだと載っていない気がします。
これがなぜわかりづらいかというと、「2. イベント後にさせたい処理」だけ開発者が書いているからですね。
ここで言うと、「TableViewを表示する」というイベントがあって、そのイベントで使う行数をDelegateで開発者が指定しています。
TableViewの実装はアプリ開発者はノータッチで、Appleの提供しているUIKitの中にあるものをそのまま使うことになります。
上述のとおり、Commandを押しながらクリックすると、中身が少しだけ見られるので、UITableViewを見てみましょう。
open class UITableView : UIScrollView, NSCoding, UIDataSourceTranslating {
public init(frame: CGRect, style: UITableView.Style) // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
(中略)
weak open var dataSource: UITableViewDataSource?
weak open var delegate: UITableViewDelegate?
あ、やっぱこんな感じで変数として、delegateとdataSourceを持っているんですね。
ではDelegateの方を見てみましょう。
今回の例だと、tableView(_ tableView, numberOfRowsInSection section)を使っているので、その関数定義があるUITableViewDataSourceの中身を見てみましょう。
public protocol UITableViewDataSource : NSObjectProtocol {
@available(iOS 2.0, *)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
ありましたね。
ちなみにDelegate定義を見るとめちゃくちゃたくさん関数があって、これ全部実装してないのに動くぞ……? と思うかもしれませんが、
option指定になっている関数は、実装してなくても怒られません。
optionがついていない、たとえばtableView(_ tableView, numberOfRowsInSection section)を実装していない状態だと、エラーでコンパイルできなくなるはずです。
(UITableViewDataSourceに準拠したクラスであれば)
ここでいうインスタンス化というのは、Interface Builderを使ってGUIでやったので、@IBOutletの部分です。
iOSアプリ開発者がDelegate先で書かなければいけないのは、3つあります。
- Delegate先をprotocolに準拠させること
- Delegate先を記述すること
- protocolで指定された関数を記述すること
3つとも、前述のコード例でやっていましたが、それぞれに対応している部分を抜粋します。
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
TableView.delegate = self
TableView.dataSource = self
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
最初「TableView.delegate = self」ってなんだ? とずっと思っていました。
selfはここではViewController自身を意味しています。
TableViewのインスタンスのDelegate先をViewControllerにしている、という意味になります。
おまけ
Twitterやってます。エンジニアの知り合いが全然いないので、積極的につながりたいです!
よろしければフォローお願いします。