「このコールバックはデリゲートにしますか、それともクロージャで受けますか?」iOSのコードレビューで特によく出る質問です。
どちらも「何か起きたら知らせて」という同じ要件に対応する手段なので、機能だけを見ればどちらでも実装できます。
UIKitはデリゲートだらけです(UITableViewDelegate、UITextFieldDelegate)。一方、Appleの新しいAPIはクロージャを受け取り、チームごとに規約も異なります。
この記事では、同じ例を2つの方式で並べて実装し、違いを明らかにしたうえで選択基準を整理します。
デリゲートパターンの構文やオブザーバーとの比較は別の記事で、クロージャのキャプチャや循環参照の仕組みはクロージャ編で解説しました。ここでは両記事の接点である「選択」に集中します。
同じ問題に2つの答え — 並べて比較する
画像ピッカー画面を作るとしましょう。ユーザーが写真を選んだら、呼び出し元の画面に知らせる必要があります。
デリゲート版です。
protocol ImagePickerDelegate: AnyObject {
func imagePicker(_ picker: ImagePickerVC, didSelect image: UIImage)
func imagePickerDidCancel(_ picker: ImagePickerVC)
}
final class ImagePickerVC: UIViewController {
weak var delegate: ImagePickerDelegate?
private func selectionDone(_ image: UIImage) {
delegate?.imagePicker(self, didSelect: image)
}
}
// 呼び出し側
extension ProfileVC: ImagePickerDelegate {
func imagePicker(_ picker: ImagePickerVC, didSelect image: UIImage) {
avatarView.image = image
}
func imagePickerDidCancel(_ picker: ImagePickerVC) { /* 無視 */ }
}
クロージャ版です。
final class ImagePickerVC: UIViewController {
var onSelect: ((UIImage) -> Void)?
var onCancel: (() -> Void)?
private func selectionDone(_ image: UIImage) {
onSelect?(image)
}
}
// 呼び出し側
let picker = ImagePickerVC()
picker.onSelect = { [weak self] image in
self?.avatarView.image = image
}
動作は同じです。違いは構造にあります。
デリゲートは通信契約をプロトコルという型として先に宣言し、受け手が契約全体に準拠します。クロージャは契約を設けず、イベントごとに値として受け渡します。
この構造上の違いが、次の実用的な差につながります。
5つの実用的な違い
1つ目は、イベントが増えたときの拡張性です。 デリゲートならイベントが5個、10個に増えても、プロトコルにメソッドを追加できます。
関連するイベントが1つの契約にまとめられ、準拠側は「この画面との対話全体」を1つのextensionに集約して実装できます。UITableViewDelegateに数十個のメソッドがあっても管理できる理由です。
クロージャではイベントごとにプロパティが1つずつ増えます。3、4個を超えると設定コードが分散し、「どのコールバックを接続し忘れたか」をコンパイラが検出できません。
デリゲートでは必須メソッドの未実装がコンパイルエラーになりますが、クロージャプロパティはnilのまま静かに通過します。
2つ目は、設定箇所との距離です。 クロージャの強みは、イベントを消費するコードをイベント発生元の呼び出し直後に置けることです。
ピッカーを開くコードと結果を受け取るコードが3行以内にあるため、流れを一目で読めます。デリゲートでは設定(delegate = self)と実装(extension)がファイル内で離れています。
一度きりのインタラクションには、その作法は大げさです。ネットワーク要求の完了、アラートボタンへの応答、アニメーション終了のような「一度起きて終わる」イベントでクロージャが標準になった理由です。
3つ目は、状態とアイデンティティです。 デリゲートメソッドでは慣例として送信元が第1引数に入ります(imagePicker(_:didSelect:)のpicker)。
1つの画面でテーブルビューを2つ使う場合、どちらから来たイベントかを区別できるのは、この慣例のおかげです。
クロージャでは送信元が自動的に渡されないため、同じ状況ならクロージャを2つ接続するか、送信元を引数として明示的に設計する必要があります。
4つ目は、メモリ管理の落とし穴がある場所です。 どちらにも循環参照の危険がありますが、落とし穴の形は異なります。
デリゲートは宣言時にweak var delegate一度指定すれば終わりで、慣例も強いためミスは多くありません。クロージャは接続する場所ごとに[weak self]を判断する必要があります。
ARC(Automatic Reference Counting、自動参照カウント)の記事で整理したとおりです。所有関係のない一回限りの実行ならweakは不要ですが、プロパティに保存されるコールバックは循環参照の候補になります。
その判断を利用箇所ごとに繰り返すわけです。ミスが入り込む余地はクロージャのほうが広くなります。
5つ目は、テストと再利用です。 クロージャはテストで軽量です。
モックオブジェクトを用意せず、テスト本体でコールバックを直接接続して呼び出しの有無を検証できるからです。デリゲートではテスト用のスパイククラスが必要になり、準備コードが増えます。
その代わり、デリゲートプロトコルは「このコンポーネントとの通信方法」を文書化する役割を果たします。複数の画面で同じコンポーネントを再利用する場合、契約が明示的であることが利点です。
選択基準 — イベント数・寿命・方向で決める
違いが分かったところで、基準にまとめます。3つの質問で、ほとんどのケースを整理できます。
質問1 — イベントはいくつあるか。 1つか2つならクロージャ、3つを超えて今後も増える関係ならデリゲートです。
イベントのまとまりが「対話」に近づくほど、契約(プロトコル)の価値は高まります。
質問2 — 関係の寿命はどの程度か。 要求と応答のように短く終わるならクロージャ、画面が生きている間ずっと継続的にやり取りする関係(スクロールイベント、テキスト編集中の検証)ならデリゲートです。
長く続く関係ほど、1か所のweak delegateの安全性が、利用箇所ごとに[weak self]を判断するより有利になります。
質問3 — 値を返してもらう必要があるか。 デリゲートメソッドは戻り値を持てます。
textField(_:shouldChangeCharactersIn:)が代表例です。「実行してよいか」を尋ねる問い合わせ型の通信は、デリゲートの得意分野です。
クロージャでも戻り値を設計できますが、保存されたOptionalクロージャの戻り値をどう扱うか(nilならデフォルト値は?)が不自然になりがちです。
境界が曖昧な場面ももちろんあります。そんなとき参考になる外部シグナルが、Appleの最近の方向性です。
UIActionベースのボタンハンドラーや、UICollectionViewDiffableDataSourceのクロージャプロバイダーがその例です。async/awaitへの移行(エラー処理編で見たcompletionハンドラーの世代交代)も同じ流れです。
一度きりの通信やデータ提供型の通信は、着実にクロージャ・async側へ移行しています。
一方、継続的なインタラクションの標準はデリゲートとして残っています。UITableViewDelegateやUINavigationControllerDelegateがその例です。
フレームワークにおけるこの分業構造は、上の3つの質問と正確に一致します。
アンチパターンを1つだけ挙げておきます。メソッドが1つしかないデリゲートプロトコルを、画面ごとに新しく作るコードです。
単一イベントのために、プロトコル宣言、準拠、weakプロパティ、extensionという4段階の作法を踏むのは、ほとんどの場合過剰設計です。そこはクロージャ1行で十分です。
逆に、クロージャプロパティが6、7個も付いたクラスは、デリゲートにまとめるべきサインです。
まとめ
- デリゲートとクロージャは、同じ要件(「何か起きたら知らせて」)に対する2つの実装です。違いの根本は、契約を型(プロトコル)として宣言するか、イベントを値として受け渡すかにあります。
- 実用上の5つの違いは、イベントの拡張性と未実装の検出はデリゲート、設定箇所の凝集はクロージャ、送信元の識別はデリゲートの慣例、メモリ上の落とし穴の広さはクロージャ、テストの軽さはクロージャが優位です。
- 選択は3つの質問で決まります。イベントは3つ以上か、関係は長く続くか、戻り値は必要か。すべて「いいえ」ならクロージャ、1つでも強く「はい」ならデリゲートです。
- 単一メソッドのデリゲートプロトコルと、5、6個のクロージャプロパティは、互いに逆方向の過剰設計サインです。
2つのツールをさらに詳しく知りたい方は、あわせて読みたい記事をご覧ください。デリゲートパターン編では、オブザーバーとの比較と1対1通信の定石を扱っています。
クロージャ完全解説編では、キャプチャ・weak self・escapingを整理しています。

