【swift】イラストで分かる!具体的なDelegateの使い方。


ディップAdventCalendar2016、23日目です。(。・ω・)ノ


※Delegateの書き方を理解するに重点を置いてますので、Delegateの本来の使い方を知りたい方には向いておりません。
あくまでも取っ掛かりという目的で閲覧くださいm(_ _)m

swift初心者の多くは苦戦するDelegate。
まず最初にぶつかる壁と言っても良いでしょう。

私もその1人で、
分かりにくい分たくさんのDelegateの記事が存在していますが、
ありとあらゆる「わかりやすい!簡単!Delegate」の記事を見てもなかなかピンときませんでした..orz
(私の理解力の問題ですが..)

ということで、
世に溢れているわかりやすいDelegateの説明を見ても理解できなかった私が、
イラストを使ってさらにわかりやすく、Delegateの基本を書いていきたいと思います。

1.そもそもなぜ理解できないのか?

まず、そもそもなぜDelegateの理解が難しいのか。

端的に言いますと、結局のところ
「で、何ができるの?😰😰」
「使いどころがわからない..😰😰」
ってところに尽きるのかなと思います。

それだと結局、
「サンプルコード見て書き方は分かったよ、うん(でも自分で流用できない)」
= 理解できていない
となってしまうので、そこを紐解く必要があります。

2.Delegateとは?

プロトコルとデリゲートのとても簡単なサンプルについて
こちらの記事を参考にさせていただきますと、

Delegation is a design pattern that enables a class or structure to hand off(or delegate) some of its responsibilities to an instance of another type.
(The Swift Programing Language記載)

=『デリゲートは、デザインパターンです。』

「ん、デザインパターンって何😰」..となるので、ここは割愛。

結局のところDelegateは、
「あるクラスは、他のクラスのインスタンスに、処理を任せることができる。」
もの、なのですが、

とりあえず「2つのクラス間で処理を跨ぐんだな😃」くらいで大丈夫です。

3.具体例

まずは、具体的な使い方を見てみようと思います。
今回はよりDelegateの書き方がピンとくるように、
「Delegateを使った場合」「使わなかった場合」の2パターンで説明しようと思います。

作るもの

こんなの。

構造としてはこのような感じです。
このぺージの親はViewControllerですね。

階層の違い

この2つのパターンは今回の場合クラス構成が一番の相違点で、
tableViewが親のViewControllerのクラスで処理が書かれているか否かに違いがあります。

■Delegateなし

■Delegateあり

押さえておく点

※解説の前に押さえておくべきこと。

今回のサンプルは
tableViewCellをタップした際にページ遷移する
つまり、そのぺージから別のぺージに移動する、ということです。
ということは、
そのぺージを作る一番親が別のページに遷移するを実行しなければなりません。
ここが重要です。

ソースの違い

各々のDelegateが絡んでいる部分の書き方の違う部分をとりあえず記載。
下記2つはまったく同じ処理をしている部分です。

具体的には、didSelectRowAt indexPath:の書き方に違いがあります。
didSelectRowAt indexPath:とはTableView上のcellがタップされた際に呼ばれるメソッドです。

■Delegateなし

親(ViewController)
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var tableView: UITableView!

    //〜省略〜

    // MARK: - UITableViewDelegate
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        guard let url = URL(string: "https://www.youtube.com/watch?v=GNIkcjccZlw") else {
            return
        }

        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url, options: [], completionHandler: nil)
        }
    }
}

■Delegateあり

子(DelegateTableView)
class DelegateTableView: UITableView, UITableViewDataSource, UITableViewDelegate {

    //delegateを設定
    var testdelegate: TestDelegate?

    //〜省略〜

    // MARK: - UITableViewDelegate
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.deselectRow(at: indexPath, animated: true)

        self.testdelegate?.test()
    }
}
Delegate
protocol TestDelegate: class {

    func test()
}
親(ViewController)
class ViewController: UIViewController, TestDelegate {

    @IBOutlet weak var delegateTableView: DelegateTableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        //子(DelegateTableView)の設定しているdelegateを自身にもセット
        delegateTableView.testdelegate = self
    }

    func test() {
        guard let url = URL(string: "https://www.youtube.com/watch?v=GNIkcjccZlw") else {
            return
        }

        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url, options: [], completionHandler: nil)
        }
    }
}

イメージ図

上の処理は絵で表すとこんな状況。

■Delegateなし

■Delegateあり



ポイント

変数の受け渡し

子(DelegateTableView)
//delegateを設定
var testdelegate: TestDelegate?
親(ViewController)
//子(DelegateTableView)の設定しているdelegateを自身にもセット
delegateTableView.testdelegate = self

別クラスで定義されている変数を自身も持つことで
自分がそのdelegateを処理することができるようになります。
この辺りの受け渡しはミスしないように注意してください。

※この=selfはTestDelegateを継承しないとErrorとなり、
delegateTableView.testdelegate = self as! TestDelegateと補完しようとするのでこちらも注意です。

protocol

Delegate
protocol TestDelegate: class {

    func test()
}

protocol (名前)と書くことで、継承できるようになります。

また、もともとswiftに備わっている、UITableViewDelegateは、
継承した際、cellForRowAt indexPath:numberOfRowsInSection section:の2つのメソッドを書かなければ

このようなエラーをはきますよね、

これと同様に、上記の例だと、
testDelegateを継承したクラスでfunc test()を書かなければエラーとなります。

補足

処理を追いやすくする手として、extensionで書くことを検討するのもありです。

class ViewController: UIViewController {
    @IBOutlet weak var delegateTableView: DelegateTableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        //子(DelegateTableView)の設定しているdelegateを自身にもセット
        delegateTableView.testdelegate = self
    }
}

extension ViewController: TestDelegate {
func test() {
        guard let url = URL(string: "https://www.youtube.com/watch?v=GNIkcjccZlw") else {
            return
        }

        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url, options: [], completionHandler: nil)
        }
    }
}

処理の場所

先ほど述べた「押さえておく点」の通り、
画面が遷移する処理である、

guard let url = URL(string: "https://www.youtube.com/watch?v=GNIkcjccZlw") else {
    return
}

if UIApplication.shared.canOpenURL(url) {
    UIApplication.shared.open(url, options: [], completionHandler: nil)
}

の処理はViewControllerに記載しています。
そこはDelegate使う使わないに限らず変わらないです。

4.まとめ

今回は少しでもわかりやすいようにDelegate有無の書き方で分けてみましたが、
delegateを使う大きなメリットは

  • 移譲先を意識する必要がない
  • 再利用ができる

なので、メリットの実感はあまりないサンプルかもしれません。笑
が、少しでも参考になれば幸いです。

ご指摘あればコメントよろしくお願いします。