Help us understand the problem. What is going on with this article?

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

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


kaisou9.png

※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パターンで説明しようと思います。

作るもの

こんなの。
kaisou.png

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

kaisou2.png

階層の違い

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

■Delegateなし

kaisou3.png

■Delegateあり

kaisou4.png

押さえておく点

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

今回のサンプルは
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なし

kaisou5.png

■Delegateあり

kaisou6.png
kaisou7.png
kaisou8.png

ポイント

変数の受け渡し

子(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 (名前)と書くことで、継承できるようになります。
朝.png

また、もともとswiftに備わっている、UITableViewDelegateは、
継承した際、cellForRowAt indexPath:numberOfRowsInSection section:の2つのメソッドを書かなければ
スクリーンショット 2016-12-23 18.15.00.png
このようなエラーをはきますよね、

これと同様に、上記の例だと、
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を使う大きなメリットは

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

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

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

narukun
バイトル(dip)→ジモティー swiftでiOSアプリ開発してます、iOSDCに登壇したりしてますʕ•ᴥ• ʔ お絵描きと世界史とドライブが好きです (twitterは完全な車垢><)
jmty
日本最大のクラシファイドサイト「ジモティー」を開発・運営するスタートアップ
http://jmty.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away