はじめに
複数のオブジェクトが互いにイベント通知を行う場合、やみくもにオブジェクト同士を参照させ合えば良いわけではありません。なぜなら、依存関係が複雑になりすぎてメンテナンスが不可能になってしまったり、メモリリークが発生してしまったりする可能性があるからです。
そこでイベント通知を行う方法として、デリゲートパターン、クロージャ、オブザーバパターンの3つの方法が主に扱われます。今回はそのうちのデリゲートパターンについて解説したいと思います。
デリゲートパターンとは
デリゲートパターンはデリゲート(委譲)という名前の通り、あるオブジェクトの処理を別のオブジェクトに代替させるパターンです。デリゲート元のオブジェクトは適切なタイミングで、デリゲート元のオブジェクトにメッセージを送ります。デリゲート先のオブジェクトはメッセージを受けて、自分自身や別のオブジェクトの状態を変更したり、何かしらの結果をデリゲート元のオブジェクトに返したりします。
メリット
デリゲートパターンを用いると、デリゲート先のオブジェクトを切り替えることでデリゲート元の振る舞いを柔軟に変更できます。
デメリット
一方で、必要な処理をプロトコルとして事前に宣言されている必要があり、記述するコードが多くなりがちです。
実装方法
デリゲートパターンでは、委譲する処理をプロトコルのメソッドとして宣言します。デリゲート先のオブジェクトはそのプロトコルに準拠し、デリゲート元のオブジェクトからの処理の委譲に応えられるようにします。デリゲート元のオブジェクトはデリゲート先のオブジェクトをプロパティとしてもち、デリゲート先のメソッドを実行して処理を委譲します。
例として、以下のようなサンプルアプリを紹介します。
内容としては、NextButtonを押すと次の画面に遷移し、textFieldに文字を記入してSaveボタンを押すと、元の画面のLabelに文字を表示するようになっています。
以下がコード内容です。
protocol NextViewControllerDelegate: AnyObject {
func didSave(text: String)
func didCancel()
}
class ViewController: UIViewController {
@IBOutlet private weak var textLabel: UILabel!
private func presentNextViewController() {
let nextVC = UIStoryboard(name: "Next", bundle: nil).instantiateInitialViewController()
as! NextViewController
nextVC.delegate = self
let nav = UINavigationController(rootViewController: nextVC)
present(nav, animated: true)
}
@IBAction func nextButton(_ sender: Any) {
presentNextViewController()
}
}
extension ViewController: NextViewControllerDelegate {
func didSave(text: String) {
textLabel.text = text
dismiss(animated: true)
}
func didCancel() {
dismiss(animated: true)
}
}
class NextViewController: UIViewController {
weak var delegate: NextViewControllerDelegate?
@IBOutlet private weak var textField: UITextField!
@IBAction func saveButton(_ sender: Any) {
guard let text = textField.text else { return }
delegate?.didSave(text: text)
}
@IBAction func cancelButton(_ sender: Any) {
delegate?.didCancel()
}
}
利用するべき時
⑴2つのオブジェクト間で多くの種類のイベント通知を行う
2つのオブジェクト間で多くの種類のイベント通知を実現したい場合、デリゲートパターンはたいへん有効です。非同期処理中に発生するイベントに応じて実行する関数を切り替えたい場合などはデリゲートパターンを利用するのが良いでしょう。
⑵外部からのカスタマイズを前提としたオブジェクトを設計する
オブジェクトの中には外部からのカスタマイズを前提とした設計が適しているものがあり、そのようなケースではデリゲートパターンを採用するのが良いでしょう。
デリゲートパターンでは、カスタマイズ可能な処理をプロトコルとして定義するため、オブジェクトのどの振る舞いがカスタマイズ可能かは明らかです。例えばUITableViewクラスの場合、画面によって異なるセルの選択時の動作をデリゲート先に委譲できるようにしています。