Delegate: що це, патерн делегування та протоколи iOS

Автор: IT Sectr Опубліковано: 2026-02-17 Час читання: 9 хв

Delegate — це патерн проєктування, за якого один об'єкт делегує виконання завдання іншому об'єкту. В iOS патерн реалізується через протоколи Swift та @protocol Objective-C. Делегування — один із фундаментальних патернів 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

Вибір між делегатом, сповіщеннями та замиканнями залежить від кількості отримувачів та зв'язаності компонентів. Кожен механізм вирішує задачу комунікації між об'єктами, але з різними компромісами.

ХарактеристикаDelegateNotificationCenterClosure
Тип зв'язку1:11:N1:1
Зв'язаністьСлабка (через протокол)Дуже слабка (рядок-ключ)Середня (захоплення контексту)
ТипобезпекаПовнаВідсутня (Any?)Повна
Ризик retain cycleНемає (weak)НемаєЄ (захоплення self)
Коли застосовуватиСкладні колбеки з кількома методамиПодії, що цікавлять багатьохПрості замикання з 1-2 колбеками

Delegate оптимальний, коли потрібно передати серію пов'язаних подій одному отримувачу. 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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