LoginSignup
105
95

More than 3 years have passed since last update.

SwiftにおけるDelegateとは何か、なぜ使うのか

Last updated at Posted at 2019-03-03

Delegateまわりは正直よく理解できていませんでした。
ところが、先日ふとコード書いてたら、突然理解できました。
解説はすでにわかりやすいのがたくさんあるので簡単に書かせて頂いて、
オリジナリティ要素として「なぜ使うのか」部分にしぼって書きたいかなと思っています。

よくわかる解説記事

QiitaでDelegateというと、下記の記事2つあたりが有名どころっぽいです。

プロトコルとデリゲートのとても簡単なサンプルについて
【swift】イラストで分かる!具体的なDelegateの使い方。

正直サンプルコードとかは上の記事を見てもらうのがシンプルだし、
さらに噛み砕いて説明が欲しければ下の記事でいいのかなと。

Delageteとは

デザインパターンの一つです。
デザインパターンっていっても、GoFのデザインパターンの中にはないです。
(これが最初個人的に混乱しました)

あるクラスから、他のクラスに処理を「移譲」(Delegate)するパターンのことです。
初心者が自分から思いついて書こうとは思わないでしょうが、
UIKitの中にある部品(UITextFieldとか)がDelegateして使うのが推奨されていて、
初心者向けの解説でも容赦なく出てくるので、そこで初心者の詰まりポイントになりがちです。

サンプルコードは上で紹介した「とても簡単なサンプル」を見るのがいいと思います(怠慢)。
初心者向けの解説本とかだと、おまじない的な扱いになってるんじゃないかなーと。
ちなみに.NET Frameworkとかにもあるみたいです。

Swiftにおいては、protocolを使って、Delegateパターンを実現します。
Xcode上で見たいプロトコルをCommandを押しながらクリックすると、
Jump to Definitionという選択肢が出るので、それを見るとプロトコルの中身が見られます。
たとえばXMLのパースする際のDelegateですが、

XMLParserDelegate
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. イベント後にさせたい処理
でわけて書きたい。
1が発生したら、2に処理を委譲するような設計に……はっ、Delegate!

よくある例(2019/05/03追記)

Delegateが壁になるのは、開発者が意図してDelegateを使っているのではなくて、
UIKitとか、iOSアプリ開発のライブラリ使おうとしたときに、Delegateを使わざるを得なくなるからだと思います。
最初に紹介したDelegateのとても簡単なサンプルでもいいのですが、もう少しリアルな例で説明したいと思います。

UITableViewを使いたい(たとえばTwitterのUI)ときは、Delegateを使うのが必須になります。
たとえば下記のようなコードになるかと。

TableViewの例

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を見てみましょう。

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の中身を見てみましょう。

Delegate定義
public protocol UITableViewDataSource : NSObjectProtocol {


    @available(iOS 2.0, *)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int

ありましたね。
ちなみにDelegate定義を見るとめちゃくちゃたくさん関数があって、これ全部実装してないのに動くぞ……? と思うかもしれませんが、
option指定になっている関数は、実装してなくても怒られません。
optionがついていない、たとえばtableView(_ tableView, numberOfRowsInSection section)を実装していない状態だと、エラーでコンパイルできなくなるはずです。
(UITableViewDataSourceに準拠したクラスであれば)

関係を整理すると、下記のような図になるかと。
ink-image.png

ここでいうインスタンス化というのは、Interface Builderを使ってGUIでやったので、@IBOutletの部分です。
iOSアプリ開発者がDelegate先で書かなければいけないのは、3つあります。

  1. Delegate先をprotocolに準拠させること
  2. Delegate先を記述すること
  3. protocolで指定された関数を記述すること

3つとも、前述のコード例でやっていましたが、それぞれに対応している部分を抜粋します。

1.Delegate先をprotocolに準拠させること
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
2.Delegate先を記述すること
        TableView.delegate = self
        TableView.dataSource = self
3.protocolで指定された関数を記述すること
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

最初「TableView.delegate = self」ってなんだ? とずっと思っていました。
selfはここではViewController自身を意味しています。
TableViewのインスタンスのDelegate先をViewControllerにしている、という意味になります。

おまけ

Twitterやってます。エンジニアの知り合いが全然いないので、積極的につながりたいです!
よろしければフォローお願いします。

105
95
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
95