あるオブジェクトが別のオブジェクトに処理を依頼したり、
特定の処理の開始や終了を伝える際にオブジェクト間でのイベントの通知が発生します。
複数のオブジェクトが互いにイベント通知を行う場合、
ただやみくもにオブジェクトどうしを参照させ合えばいいわけではありません。
そのような実装をしてしまうと、依存関係が複雑になりすぎて
メンテナンスが不可能になってしまったり、メモリリークが発生する可能性があります。
オブジェクト間のイベント通知の方法は次の3つが存在します。
・デリゲートパターン
・クロージャ
・オブザーバパターン
今回は、この中のデリゲートパターンについて説明します。
私もしっかりと理解できていなかったので調べながら記事を作成しました。
なので至らない点があるかもしれませんがご了承ください。
デリゲートパターン
デリゲートパターンはデリゲート(委譲)という名の通り、
あるオブジェクトの処理を別のオブジェクトに任せるパターンになります。
デリゲート元のオブジェクトは適切なタイミングで、
デリゲート先のオブジェクトにメッセージを送ります。
デリゲート先のオブジェクトはメッセージを受けて、
自分自身や別のオブジェクトの状態を変更したり、何かしらの結果を返したりします。
実装方法
デリゲートパターンでは、委譲する処理をプロトコルのメソッドとして宣言します。
デリゲート先のオブジェクトはそのプロトコルに準拠し応えられるようにします。
デリゲートをplaygroundで説明すると面倒なので
Storyboardを使って説明してみます!
例としてカウントアップアプリの機能を作成しました。
左のViewを画面1、右のViewを画面2とします。
処理内容としては、
・+ボタンを押したら左の画面1の数字が増えていく
・次の画面ボタンを押したら画面2に画面遷移する
-> この時、画面1の数字を画面2に表示する
・戻るボタンを押すと画面1に戻る
-> この時、画面1の数字を0に戻す
です。
画面1をデリゲート先、画面2をデリゲート元にしたいと思います。
import UIKit
class ViewController: UIViewController, CountZeroDelegate {
@IBOutlet weak var countLabel: UILabel!
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
}
// プラスボタン
@IBAction func plusButton(_ sender: Any) {
count += 1
countLabel.text = String(count)
}
// 次の画面ボタン
@IBAction func nextButton(_ sender: Any) {
performSegue(withIdentifier: "next", sender: nil)
}
// performSegueが実行された時に呼ばれる
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let nextVC = segue.destination as! NextViewController
// NextViewControllerのcountプロパティの値を変更している
nextVC.count = self.count
// NextViewControllerのデリゲートを自分に委譲している
nextVC.delegate = self
}
// デリゲートメソッド
func countZero() {
count = 0
countLabel.text = String(count)
}
}
import UIKit
// デリゲートプロトコル
protocol CountZeroDelegate {
func countZero()
}
class NextViewController: UIViewController {
@IBOutlet weak var countLabel: UILabel!
var count = 0
var delegate: CountZeroDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Labelにcountの値を代入する
countLabel.text = String(count)
}
// 戻るボタン
@IBAction func backButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
// 戻るボタンを押した時に、
// 委譲しているオブジェクトにデリゲートメソッドを実行してもらう
delegate?.countZero()
}
}
これだけ見せられてもわかりにくいと思うので説明していきます。
下記がデリゲートを定義している箇所になります。
今までのプロトコルの定義と同じ方法です。
デリゲートはメソッドを定義します。
protocol CountZeroDelegate {
func countZero()
}
①のようにdelegateを定義し、
デリゲートを実行したい箇所で②のように記述します。
// ①
var delegate: CountZeroDelegate?
// ② デリゲート先に、実行してね!とイベント通知を行っている
delegate?.countZero()
delegate?.countZero()
が記述されているスコープは戻るボタンなので、
戻るボタンが押された時にcountZero()プロパティを実行させます。
実行させる対象はデリゲート先のオブジェクトです。
デリゲート先のオブジェクトが今回はViewControllerにする予定なので次のように定義します。
// デリゲート先なのでデリゲートプロトコルに準拠し応えられるようにする
class ViewController: UIViewController, CountZeroDelegate {
準拠しているプロトコルに定義されている内容は実装しなければなりませんので、
countZero( )メソッドを定義します。
func countZero() {
count = 0
countLabel.text = String(count)
}
そして、後はデリゲート元から委譲されていないといけないので、
下記のように委譲しています。
デリゲート元(NextViewController)で定義されているプロトコルの処理内容は、
こちら(デリゲート先のViewController)で定義しておくので処理を委譲してね!
というイメージです。(わかりにくい・・・)
// NextViewControllerのデリゲートを自分に委譲している
nextVC.delegate = self
説明下手なのでコードから読み取っていただけると嬉しいです・・・。
デリゲートを利用する場面としては、
2つのオブジェクト間で多くの種類のイベント通知を行う際に有効だと思います。
例えば、
① 処理を開始したタイミングでメッセージを表示する。
② 処理の途中で定期的にメッセージを表示する。
③ 処理が終了したタイミングでメッセージを表示する。
※ 処理が失敗したタイミングでメッセージを表示する。 など
デリゲートは2つのオブジェクト間で多くの種類のイベント通知に使い、
クロージャはシンプルなイベント通知(ロードが完了しました。の表示のみ)などに使い、
オブザーバは1対多のイベント通知に使うようにすればいいと思います。
以上でデリゲートの説明を終了します。
もっといい説明の仕方があったかもしれませんが
そこら辺は許していただけると助かります。
最後までご覧いただきありがとうございました。