[iOS] Delegateとはなんぞ?

実はUITableViewとかで使われているアレです。過去の自分に優しく教える気持ちで書いていきます。

Delegateとはなんぞ?

そのまま書いちゃうと、処理の委譲です。
Aさんの処理してるんだけど、この処理ってBさんがやるべき(or というかBさんしかできないよね?)というときに使います。

正直、委譲とか言われてもピンとこないので、よく使われるUITableViewを例に説明していきます。

例1. UITableViewの場合

UITableViewはご存知の通り、TableViewの生成やイベントの処理を行ってくれます。しかし、どのような見た目なのか?データはどうするのか?イベントに対してどう処理をするのか?については、実装されていません。もしされていて、単一の挙動しかしなかったら、めちゃめちゃ使いにくいですよね?
「タップされたらどのような処理をするか?」は、委譲先に任せているのです。これがDelegate パターンというものです。

委譲先は誰?

UIViewControllerにUITableViewを実装する時に、よくこれを忘れて「行のタップが取れない…」と数時間ハマっていませんでしたか?
tableView.delegate = self
この処理は、「TableViewのDelegate(委譲先)は自分だ!」と宣言する処理です。
TableViewのDelegate が設定されていないと、いくら `tableView(_:didSelectRowAt:)` などのデリゲートメソッドを実装しても、TableViewは行選択イベントの委譲先を知らされていないので、どこに処理をお願いすれば良いのかわかりません。
ViewControllerが、TableViewのDelegateは自分だよ、というのを設定してあげることで初めて、TableViewのデリゲートメソッドの振る舞いをViewControllerが制御するできるようになるのです。

UITableViewはよく使われますが実装が見えにくいので、次の例で Delegate 元/先を実装しながらコードを見てみましょう。

例2. ViewControllerとPresenterの場合

登場人物は以下の2人です。
- ViewController → 画面を作ったりタップイベントなどを処理する人 (Delegate先)
- Presenter → タップイベントをどうするか決める人 (Delegate元)

ViewControllerはタップイベントを検知するけど、画面やデータを更新するかどうかはPresenterが決めてほしい!という状況です。
*軽くMVPというアーキテクチャに則るのですが、別な話になるので割愛します。2人の役割だけ押さえておいてください。

流れを整理すると
    1. Viewはボタンタップなどのイベントを受ける
    2. Presenterに通知する(presenter.increment())
    3. Presenterはそれを受けてデータ更新などをし、delegate 経由で次の処理ViewControllerに指示する
のようになります。

実際のコードを見てみましょう。


// 委譲先はこのDelegateに準拠し、中身を実装する必要がある
// この例だと、ViewControllerがupdateCount()というメソッドがどういう処理をするか?を実装する
protocol CounterDelegate {
    func updateCount(_ count: Int)
}

class Presenter {
    private var count = 0

    private var delegate: CounterDelegate?

    func attachView(_ delegate: CounterDelegate) {
        self.delegate = delegate
    }

    func incrementCount() {
        count += 1

        // Delegate先に 「updateCount()を実行して」 と指示をする
        // この時、Delegate先はCounterDelegateに準拠していればなんでもよい(PresenterはDelegate先を知らなくて良い)
        delegate?.updateCount(count)
    }
}

class ViewController: UIViewController {
    @IBOutlet private weak var countLabel: UILabel!

    private let presenter = Presenter()

    override func viewDidLoad() {
        super.viewDidLoad()

		// presenterのDelegateはViewControllerだよ!と教えてあげる
        // これによって、updateCount()の処理がViewControllerに任されることになる。
        presenter.attachView(self)
    }

    @IBAction func tapCountupButton(_ sender: Any) {
    	// タップイベントがきたので、一旦presenterに処理を任せる
        presenter.incrementCount()
    }
}

extension ViewController: CounterDelegate {
	// Delegateに準拠し、メソッドを実行する
    // Viewの更新はViewControllerがやるので、presenterはDelegate経由でupdateCount()を実行するよう指示する
    func updateCount(_ count: Int) {
        countLabel.text = count.description
    }
}

このように、Delegateパターンを使うことで、PresenterはViewControllerが何者かはわからないが、CounterDelegateに準拠している(updateCount(count: Int) を実装している)ことがわかっているので、 ViewControllerと疎結合を保ったまま、ViewControllerのメソッドを叩くことができるのがメリットになります。

コメント