Delegate — это паттерн проектирования, при котором один объект делегирует выполнение задачи другому объекту. В iOS паттерн реализуется через протоколы Swift и @protocol Objective-C. Delegation — один из фундаментальных паттернов Cocoa Touch, используемый в UITableViewDelegate, UITextFieldDelegate и сотнях других API Apple. По данным Apple Developer Documentation (2025), около 70% системных классов UIKit используют делегаты для кастомизации поведения без наследования.
Главное
Delegate — это паттерн поведенческого проектирования, позволяющий объекту передавать часть своих обязанностей другому объекту. В отличие от наследования, где дочерний класс переопределяет методы родителя, делегат использует композицию: объект-владелец хранит ссылку на делегата и вызывает его методы в определённых точках.
Делегат определяет протокол — набор методов, которые делегат может реализовать. Методы делятся на обязательные (required) и опциональные (optional). В Swift опциональные методы протокола помечаются ключевым словом @objc optional.
Объект A (владелец) содержит свойство delegate — weak-ссылку на объект B (делегат). Когда происходит событие, A проверяет, реализует ли B соответствующий метод протокола, и вызывает его. Weak-ссылка обязательна: без неё делегат не сможет освободиться из памяти, так как владелец удерживает его сильной ссылкой.
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 гарантирует, что протокол может быть реализован только классами — это необходимо для weak-ссылки.
Реализация делегата в Swift включает три шага: объявление протокола, создание weak-свойства 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(_:). Controller выступает посредником между View и логикой валидации.
Выбор между делегатом, уведомлениями и замыканиями зависит от количества получателей и связанности компонентов. Каждый механизм решает задачу коммуникации между объектами, но с разными trade-off.
| Характеристика | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| Тип связи | 1:1 | 1:N | 1:1 |
| Связанность | Слабая (через протокол) | Очень слабая (строка-ключ) | Средняя (захват контекста) |
| Типобезопасность | Полная | Отсутствует (Any?) | Полная |
| Retain cycle риск | Нет (weak) | Нет | Есть (захват self) |
| Когда применять | Сложные колбэки с несколькими методами | События, которые интересуют многих | Простые замыкания с 1-2 колбэками |
Делегат оптимален, когда нужно передать серию связанных событий одному получателю. NotificationCenter лучше для broadcast-уведомлений. Closure — для простых асинхронных операций, таких как completion handler в 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];
}
// асинхронная загрузка изображения
}
@endКлючевое отличие Objective-C: перед вызовом опционального метода необходима проверка respondsToSelector:. В Swift опциональные методы протокола исключают эту проверку — optional chaining (?.) автоматически обрабатывает случай отсутствия реализации.
Ошибки использования делегатов приводят к утечкам памяти, падением приложения и неочевидным багам. Рассмотрим пять самых распространённых проблем.
Retain cycle — самая частая ошибка. Если свойство delegate объявлено как strong, а делегат в свою очередь владеет объектом-владельцем, образуется цикл удержания. Оба объекта никогда не будут освобождены из памяти. Решение: всегда объявлять делегата как weak var в Swift или @property (weak) в Objective-C.
Если объект-владелец переживает делегата, а ссылка осталась, вызов метода делегата приведёт к EXC_BAD_ACCESS. Weak-ссылка решает эту проблему автоматически: после освобождения делегата свойство становится nil. Однако в многопоточных сценариях стоит дополнительно проверять делегата в главном потоке.
Протокол с 20+ методами нарушает принцип разделения интерфейсов (ISP). UITableViewDelegate содержит около 30 опциональных методов — это исторически сложившееся исключение. В собственных протоколах лучше разделить ответственность на несколько мелких протоколов, каждый со своей ролью.
Системные API Apple активно используют паттерн Delegate. Рассмотрим три ключевых примера из UIKit, которые встречаются в каждом iOS-приложении.
| API | Протокол | Ключевые методы |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
Каждый из этих протоколов реализует разные аспекты поведения: UITableViewDelegate управляет внешним видом и реакцией на касания, UITextFieldDelegate контролирует ввод текста, URLSessionDelegate обрабатывает сетевые события. Это демонстрирует гибкость паттерна: делегат может быть адаптирован под любую область ответственности.
Часто задаваемые вопросы
Delegate управляет поведением и внешним видом (высота ячейки, реакция на нажатие). DataSource предоставляет данные (количество строк, содержимое ячеек). В UITableViewDelegate и UITableViewDataSource — это два отдельных протокола, разделяющих ответственность за представление и данные.
Weak-ссылка предотвращает retain cycle. Владелец (например, UITableView) удерживает ссылку на делегата только как weak. Если делегат (UIViewController) владеет таблицей, strong-ссылка на делегата создала бы цикл: ViewController → UITableView → Delegate (ViewController). Weak разрывает этот цикл.
В SwiftUI паттерн Delegate используется реже — его заменяют @Binding, @State и замыкания. Однако delegate по-прежнему применяется для интеграции UIKit через UIViewRepresentable. Например, MKMapViewDelegate и WKUIDelegate остаются актуальными при оборачивании UIKit-компонентов в SwiftUI.
@objc optional позволяет объявить необязательные методы в протоколе Swift. Это механизм совместимости с Objective-C runtime. Без @objc все методы протокола Swift по умолчанию обязательны. Optional используется в UIKit-протоколах, где делегат может реализовать только нужные ему методы.
У одного объекта может быть только один делегат для каждого свойства delegate. Если нужно оповестить несколько объектов, используйте multicast delegate, массив делегатов или NotificationCenter. Паттерн Delegate изначально спроектирован как отношение 1:1.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также