Delegate: что это, паттерн делегирования и протоколы iOS

Автор: IT Sectr Опубликовано: 2026-02-17 Время чтения: 9 мин

Delegate — это паттерн проектирования, при котором один объект делегирует выполнение задачи другому объекту. В iOS паттерн реализуется через протоколы Swift и @protocol Objective-C. Delegation — один из фундаментальных паттернов Cocoa Touch, используемый в UITableViewDelegate, UITextFieldDelegate и сотнях других API Apple. По данным Apple Developer Documentation (2025), около 70% системных классов UIKit используют делегаты для кастомизации поведения без наследования.

Главное

  • Delegate — паттерн, при котором объект A делегирует выполнение задачи объекту B через протокол или интерфейс
  • Протокол Swift определяет набор методов, которые делегат может или должен реализовать
  • Weak-ссылка на делегата обязательна для предотвращения retain cycle и утечек памяти
  • UITableViewDelegate — самый распространённый пример делегата в iOS с 20+ опциональными методами
  • Отличие от Observer: делегат — отношение 1:1, в то время как уведомления работают по принципу 1:N

Что такое паттерн Delegate?

Delegate — это паттерн поведенческого проектирования, позволяющий объекту передавать часть своих обязанностей другому объекту. В отличие от наследования, где дочерний класс переопределяет методы родителя, делегат использует композицию: объект-владелец хранит ссылку на делегата и вызывает его методы в определённых точках.

Делегат определяет протокол — набор методов, которые делегат может реализовать. Методы делятся на обязательные (required) и опциональные (optional). В Swift опциональные методы протокола помечаются ключевым словом @objc optional.

Принцип работы делегата

Объект A (владелец) содержит свойство delegate — weak-ссылку на объект B (делегат). Когда происходит событие, A проверяет, реализует ли B соответствующий метод протокола, и вызывает его. Weak-ссылка обязательна: без неё делегат не сможет освободиться из памяти, так как владелец удерживает его сильной ссылкой.

Swift
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-ссылки.

Как работает Delegate в Swift?

Реализация делегата в Swift включает три шага: объявление протокола, создание weak-свойства delegate во владельце и реализация протокола в классе-делегате. Рассмотрим на примере кастомного UITextField с валидацией.

Swift
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 и логикой валидации.

Delegate vs NotificationCenter vs Closure

Выбор между делегатом, уведомлениями и замыканиями зависит от количества получателей и связанности компонентов. Каждый механизм решает задачу коммуникации между объектами, но с разными trade-off.

ХарактеристикаDelegateNotificationCenterClosure
Тип связи1:11:N1:1
СвязанностьСлабая (через протокол)Очень слабая (строка-ключ)Средняя (захват контекста)
ТипобезопасностьПолнаяОтсутствует (Any?)Полная
Retain cycle рискНет (weak)НетЕсть (захват self)
Когда применятьСложные колбэки с несколькими методамиСобытия, которые интересуют многихПростые замыкания с 1-2 колбэками

Делегат оптимален, когда нужно передать серию связанных событий одному получателю. NotificationCenter лучше для broadcast-уведомлений. Closure — для простых асинхронных операций, таких как completion handler в URLSession.

Реализация Delegate в Objective-C

Objective-C использует @protocol и @optional для объявления делегатов. В отличие от Swift, все методы протокола по умолчанию опциональны. Ключевое отличие — вызов respondsToSelector: перед отправкой сообщения делегату, так как метод может быть не реализован.

Objective-C
@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.

Делегат не обнулён после dealloc

Если объект-владелец переживает делегата, а ссылка осталась, вызов метода делегата приведёт к EXC_BAD_ACCESS. Weak-ссылка решает эту проблему автоматически: после освобождения делегата свойство становится nil. Однако в многопоточных сценариях стоит дополнительно проверять делегата в главном потоке.

Чрезмерное количество методов в протоколе

Протокол с 20+ методами нарушает принцип разделения интерфейсов (ISP). UITableViewDelegate содержит около 30 опциональных методов — это исторически сложившееся исключение. В собственных протоколах лучше разделить ответственность на несколько мелких протоколов, каждый со своей ролью.

Примеры Delegate в системных API iOS

Системные API Apple активно используют паттерн Delegate. Рассмотрим три ключевых примера из UIKit, которые встречаются в каждом iOS-приложении.

APIПротоколКлючевые методы
UITableViewUITableViewDelegatedidSelectRowAt, heightForRowAt, willDisplay
UITextFieldUITextFieldDelegateshouldChangeCharactersIn, didBeginEditing, shouldReturn
URLSessionURLSessionDelegatedidReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError

Каждый из этих протоколов реализует разные аспекты поведения: UITableViewDelegate управляет внешним видом и реакцией на касания, UITextFieldDelegate контролирует ввод текста, URLSessionDelegate обрабатывает сетевые события. Это демонстрирует гибкость паттерна: делегат может быть адаптирован под любую область ответственности.

Часто задаваемые вопросы

В чём разница между delegate и dataSource в iOS?

Delegate управляет поведением и внешним видом (высота ячейки, реакция на нажатие). DataSource предоставляет данные (количество строк, содержимое ячеек). В UITableViewDelegate и UITableViewDataSource — это два отдельных протокола, разделяющих ответственность за представление и данные.

Почему delegate объявляется как weak?

Weak-ссылка предотвращает retain cycle. Владелец (например, UITableView) удерживает ссылку на делегата только как weak. Если делегат (UIViewController) владеет таблицей, strong-ссылка на делегата создала бы цикл: ViewController → UITableView → Delegate (ViewController). Weak разрывает этот цикл.

Можно ли использовать delegate в SwiftUI?

В SwiftUI паттерн Delegate используется реже — его заменяют @Binding, @State и замыкания. Однако delegate по-прежнему применяется для интеграции UIKit через UIViewRepresentable. Например, MKMapViewDelegate и WKUIDelegate остаются актуальными при оборачивании UIKit-компонентов в SwiftUI.

Что такое @objc optional в протоколах Swift?

@objc optional позволяет объявить необязательные методы в протоколе Swift. Это механизм совместимости с Objective-C runtime. Без @objc все методы протокола Swift по умолчанию обязательны. Optional используется в UIKit-протоколах, где делегат может реализовать только нужные ему методы.

Сколько делегатов может быть у одного объекта?

У одного объекта может быть только один делегат для каждого свойства delegate. Если нужно оповестить несколько объектов, используйте multicast delegate, массив делегатов или NotificationCenter. Паттерн Delegate изначально спроектирован как отношение 1:1.

Итоги

  • Delegate — паттерн поведенческого проектирования, передающий выполнение задачи от одного объекта другому через протокол
  • Протокол Swift объявляет набор методов; опциональные методы помечаются @objc optional, обязательные реализуются всегда
  • Weak-ссылка на делегата обязательна для предотвращения retain cycle и утечек памяти
  • UITableViewDelegate — наиболее известный пример; его методы управляют высотой, выделением и отображением ячеек
  • Delegate отличается от NotificationCenter отношением 1:1 против 1:N и полной типобезопасностью
  • Objective-C требует проверки respondsToSelector: для опциональных методов, Swift обрабатывает это автоматически
  • Рекомендация: используйте delegate для сложных колбэков с несколькими связанными методами и closure для простых замыканий

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также