Delegateは、あるオブジェクトがタスクの実行を別のオブジェクトに委譲するデザインパターンです。iOSでは、このパターンはSwiftプロトコルと@protocol Objective-Cを通じて実装されます。委譲はCocoa Touchの基本的なパターンの1つであり、UITableViewDelegate、UITextFieldDelegate、そして何百もの他のApple APIで使用されています。Apple Developer Documentation(2025)によると、UIKitシステムクラスの約70%が継承なしで動作をカスタマイズするためにデリゲートを使用しています。
重要なポイント
Delegateは、オブジェクトがその責任の一部を別のオブジェクトに委譲できるようにする動作デザインパターンです。子クラスが親メソッドをオーバーライドする継承とは異なり、デリゲートはコンポジションを使用します。所有者オブジェクトはデリゲートへの参照を保持し、特定のポイントでそのメソッドを呼び出します。
デリゲートはプロトコルを定義します — デリゲートが実装できるメソッドのセットです。メソッドは必須(required)とオプショナル(optional)に分けられます。Swiftでは、オプショナルなプロトコルメソッドは@objc optionalキーワードでマークされます。
オブジェクトA(所有者)はdelegateプロパティ — オブジェクトB(デリゲート)への弱参照(weak reference)を含みます。イベントが発生すると、AはBが対応するプロトコルメソッドを実装しているか確認し、それを呼び出します。弱参照は必須です。これがないと、所有者が強い参照で保持するため、デリゲートはメモリから解放されません。
protocol LoaderDelegate: AnyObject {
func loaderDidStart(_ loader: DataLoader)
func loader(_ loader: DataLoader, didLoad data: Data)
func loader(_ loader: DataLoader, didFailWith error: Error)
}
class DataLoader {
weak var delegate: LoaderDelegate?
func start() {
delegate?.loaderDidStart(self)
// 非同期ローディング
}
}DataLoaderクラスはLoaderDelegateプロトコルを定義し、ロードライフサイクルの重要なポイントでデリゲートメソッドを呼び出します。AnyObjectは、プロトコルがクラスによってのみ実装できることを保証します — これは弱参照のために必要です。
Swiftでのデリゲートの実装は3つのステップを含みます:プロトコルの宣言、所有者での弱いdelegateプロパティの作成、デリゲートクラスでのプロトコルの実装です。検証付きのカスタムUITextFieldの例を見てみましょう。
protocol ValidatorDelegate: AnyObject {
func validate(_ input: String) -> Bool
func validatorDidFail(_ input: String)
}
class ValidatedTextField: UITextField {
weak var validator: ValidatorDelegate?
override func textDidChange() {
guard let text = self.text else { return }
if validator?.validate(text) == false {
validator?.validatorDidFail(text)
self.layer.borderColor = UIColor.red.cgColor
}
}
}
class LoginViewController: UIViewController, ValidatorDelegate {
let textField = ValidatedTextField()
override func viewDidLoad() {
super.viewDidLoad()
textField.validator = self
}
func validate(_ input: String) -> Bool {
return input.count >= 6
}
func validatorDidFail(_ input: String) {
print("検証失敗:入力が短すぎます")
}
}LoginViewControllerはValidatorDelegateプロトコルを実装し、自身をtextFieldのデリゲートとして設定します。テキストが変更されるたびに、ValidatedTextFieldはvalidate(_:)を呼び出し、検証が失敗した場合はvalidatorDidFail(_:)を呼び出します。コントローラーはViewと検証ロジックの間の仲介役として機能します。
デリゲート、通知、クロージャーの選択は、受信者の数とコンポーネントの結合度に依存します。各メカニズムはオブジェクト間の通信の問題を解決しますが、異なるトレードオフがあります。
| 特性 | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| 通信タイプ | 1:1 | 1:N | 1:1 |
| 結合度 | 弱い(プロトコル経由) | 非常に弱い(文字列キー) | 中程度(コンテキストキャプチャ) |
| 型安全性 | 完全 | なし(Any?) | 完全 |
| 保持サイクルのリスク | なし(weak) | なし | あり(selfキャプチャ) |
| 使用する場面 | 複数のメソッドを持つ複雑なコールバック | 多くの人が関心を持つイベント | 1-2のコールバックを持つ単純なクロージャー |
Delegateは、関連する一連のイベントを単一の受信者に渡す必要がある場合に最適です。NotificationCenterはブロードキャスト通知に適しています。Closureは、URLSessionの完了ハンドラのような単純な非同期操作に使用します。
Objective-Cはデリゲートを宣言するために@protocolと@optionalを使用します。Swiftとは異なり、すべてのプロトコルメソッドはデフォルトでオプショナルです。主な違いは、メソッドが実装されていない可能性があるため、デリゲートにメッセージを送信する前にrespondsToSelector:を呼び出すことです。
@protocol ImageCacheDelegate
@optional
- (void)cacheDidStartDownload: (ImageCache *)cache;
- (void)cache: (ImageCache *)cache didCacheImage: (UIImage *)image;
@required
- (void)cache: (ImageCache *)cache didFailWithError: (NSError *)error;
@end
@interface ImageCache : NSObject
@property (nonatomic, weak) id<ImageCacheDelegate> delegate;
- (void)downloadImageAtURL: (NSURL *)url;
@end
@implementation ImageCache
- (void)downloadImageAtURL: (NSURL *)url {
if ([self.delegate respondsToSelector:@selector(cacheDidStartDownload:)]) {
[self.delegate cacheDidStartDownload:self];
}
// 非同期画像ローディング
}
@endObjective-Cの主な違い:オプショナルメソッドを呼び出す前に、respondsToSelector:の確認が必要です。Swiftでは、オプショナルなプロトコルメソッドはこの確認を不要にします — オプショナルチェイニング(?.)が実装の欠如を自動的に処理します。
デリゲート使用の間違いは、メモリリーク、アプリのクラッシュ、わかりにくいバグにつながります。5つの最も一般的な問題を見てみましょう。
保持サイクルは最も一般的な間違いです。delegateプロパティがstrongとして宣言され、デリゲートが所有者オブジェクトを所有している場合、保持サイクルが形成されます。両方のオブジェクトはメモリから解放されることはありません。解決策:常にSwiftではweak var、Objective-Cでは@property (weak)としてデリゲートを宣言します。
所有者オブジェクトがデリゲートより長生きし、参照が残っている場合、デリゲートメソッドを呼び出すとEXC_BAD_ACCESSが発生します。弱参照はこの問題を自動的に解決します:デリゲートが解放された後、プロパティはnilになります。ただし、マルチスレッドシナリオでは、メインスレッドでデリゲートを追加で確認する必要があります。
20以上のメソッドを持つプロトコルは、インターフェース分離の原則(ISP)に違反します。UITableViewDelegateには約30のオプショナルメソッドがあります — これは歴史的な例外です。独自のプロトコルでは、責任をいくつかの小さなプロトコルに分割し、それぞれに独自の役割を持たせる方が良いでしょう。
AppleのシステムAPIはDelegateパターンを積極的に使用しています。すべてのiOSアプリケーションに登場するUIKitの3つの主要な例を見てみましょう。
| API | プロトコル | 主要メソッド |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
これらのプロトコルはそれぞれ異なる動作の側面を実装しています:UITableViewDelegateは外観とタッチ応答を管理し、UITextFieldDelegateはテキスト入力を制御し、URLSessionDelegateはネットワークイベントを処理します。これはパターンの柔軟性を示しています:デリゲートはあらゆる責任領域に適応できます。
よくある質問
Delegateは動作と外観を管理します(セルの高さ、タッチ応答)。DataSourceはデータを提供します(行数、セルの内容)。UITableViewDelegateとUITableViewDataSourceでは — これらはプレゼンテーションとデータの責任を分割する2つの別個のプロトコルです。
弱参照は保持サイクルを防ぎます。所有者(例:UITableView)はデリゲートへの参照をweakとしてのみ保持します。デリゲート(UIViewController)がテーブルを所有している場合、デリゲートへの強い参照はサイクルを生成します:ViewController → UITableView → Delegate(ViewController)。Weakはこのサイクルを断ち切ります。
SwiftUIでは、Delegateパターンはあまり使用されません — 代わりに@Binding、@State、クロージャーが使用されます。ただし、デリゲートはUIViewRepresentableを通じたUIKit統合のために今でも使用されています。例えば、MKMapViewDelegateやWKUIDelegateは、UIKitコンポーネントをSwiftUIでラップする際に関連性を保っています。
@objc optionalは、Swiftプロトコルでオプショナルメソッドを宣言することを可能にします。これはObjective-Cランタイムとの互換性メカニズムです。@objcがない場合、すべてのSwiftプロトコルメソッドはデフォルトで必須です。Optionalは、デリゲートが必要なメソッドのみを実装できるUIKitプロトコルで使用されます。
1つのオブジェクトは、各delegateプロパティに対して1つのデリゲートのみを持つことができます。複数のオブジェクトに通知する必要がある場合は、マルチキャストデリゲート、デリゲートの配列、またはNotificationCenterを使用してください。Delegateパターンは元々1:1の関係として設計されています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。