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

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

Delegate — це патерн проєктування, при якому один обєкт делегує виконання завдань іншому обєкту через протокол із заздалегідь визначеними методами. У розробці iOS Delegate є одним із фундаментальних патернів Cocoa Touch, який використовується для асинхронного сповіщення без прямого зв'язку між відправником і отримувачем. За даними Apple Documentation (2025), делегування застосовується в Foundation і UIKit для обробки подій таблиць, мережевих запитів і управління місцезнаходженням. Патерн забезпечує слабку зв'язаність компонентів і повторне використання коду.

Головне

  • Delegate — патерн, при якому обєкт доручає обробку подій іншому обєкту через протокол.
  • Protocol у Swift визначає набір методів, які повинен або може реалізувати delegate.
  • Weak reference обов'язкова для властивості delegate, щоб уникнути retain cycle.
  • @objc optional дозволяє робити методи протоколу необов'язковими для реалізації.
  • URLSessionDelegate — асинхронний delegate для обробки подій мережевого запиту.

Що таке Delegate?

Delegate (делегат) — це обєкт, який реалізує певний протокол і отримує сповіщення про події іншого обєкта. Патерн Delegation є альтернативою успадкуванню: замість створення підкласу для перевизначення методів, обєкт делегує обробку подій зовнішньому обєкту. В iOS делегування реалізовано через протоколи Swift з обов'язковими та опціональними методами. Властивість delegate завжди оголошується як weak var, щоб уникнути циклічних посилань між обєктами.

Визначення протоколу Delegate

Протокол delegate визначає контракт взаємодії між обєктами. Обов'язкові методи повинні бути реалізовані делегатом, інакше код не скомпілюється. Опціональні методи позначаються атрибутом @objc optional і дозволяють делегату реагувати лише на потрібні події. Імена методів слідують конвенції: перший параметр — обєкт-відправник, другий — дані події. Наприклад, tableView(_:didSelectRowAt:) вказує, що відправник — UITableView, а дані — індекс вибраного рядка.

swift
// Протокол Delegate
protocol DownloadManagerDelegate: AnyObject {
    func downloadManager(_ manager: DownloadManager,
                          didFinishWith data: Data)
    func downloadManager(_ manager: DownloadManager,
                          didFailWith error: Error)

    @objc optional func downloadManager(_ manager: DownloadManager,
                                didUpdateProgress progress: Float)
}

// Клас, що використовує Delegate
class DownloadManager {
    weak var delegate: DownloadManagerDelegate?

    func startDownload(from url: URL) {
        URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let self else { return }
            if let error = error {
                self.delegate?.downloadManager(self, didFailWith: error)
            } else if let data = data {
                self.delegate?.downloadManager(self, didFinishWith: data)
            }
        }.resume()
    }
}

Слабке посилання на delegate

Властивість delegate обов'язково оголошується як weak var для запобігання retain cycle. Якби посилання було сильним, делегат і делегуючий обєкт тримали б один одного, і ARC не зміг би звільнити їх пам'ять. Протоколи delegate успадковують AnyObject (тільки для класів), що дозволяє використовувати weak. Структури та переліки не можуть бути делегатами через семантику значень. Альтернатива для value types — callback closures.

swift
class ViewController: DownloadManagerDelegate {
    let manager = DownloadManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        manager.delegate = self // weak — немає retain cycle
        manager.startDownload(from: url)
    }

    func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
        processData(data)
    }

    func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
        showError(error)
    }
}

Як працює патерн Delegate в iOS?

Патерн Delegate працює за принципом «один-до-одного»: один обєкт-відправник може мати лише одного делегата в конкретний момент часу. Коли відбувається подія, відправник перевіряє, чи встановлений delegate, і викликає відповідний метод протоколу. Перевага перед прямим викликом — відправник не знає тип делегата, лише те, що він відповідає протоколу. Це дотримується принципу інверсії залежностей (DIP) з SOLID.

Життєвий цикл делегата

Делегат призначається через присвоєння: someObject.delegate = self. При деалокації делегата властивість автоматично стає nil через weak semantics. Перед викликом методу делегат перевіряється через optional chaining: delegate?.method(). Якщо delegate nil — виклик ігнорується без крашу. Для опціональних методів протоколу використовується додаткова перевірка: delegate?.responds(to: #selector(...)), хоча в Swift ця перевірка зазвичай неявна через optional method declaration.

Асинхронне сповіщення через Delegate

У багатопотоковому середовищі delegate використовується для асинхронного повернення результату. URLSession надає URLSessionDelegate з методами, які викликаються при отриманні даних, таймауті або помилці аутентифікації. Методи delegate виконуються у фоновій черзі URLSession, тому потрібна диспетчеризація на main queue для UI-оновлень. Асинхронний delegate не блокує викликаючий потік, дозволяючи продовжити виконання інших завдань.

swift
class NetworkService: NSObject, URLSessionDataDelegate {
    private lazy var session = URLSession(
        configuration: .default,
        delegate: self,
        delegateQueue: OperationQueue()
    )
    private var receivedData = Data()

    func urlSession(_ session: URLSession,
                    dataTask: URLSessionDataTask,
                    didReceive data: Data) {
        receivedData.append(data)
        let progress = Float(receivedData.count) / Float(expectedSize)
        DispatchQueue.main.async {
            self.progressHandler?(progress)
        }
    }

    func urlSession(_ session: URLSession,
                    task: URLSessionTask,
                    didCompleteWithError error: Error?) {
        if let error = error {
            delegate?.networkService(self, didFailWith: error)
        } else {
            delegate?.networkService(self, didReceive: receivedData)
        }
    }
}

Delegate vs Callback: порівняння підходів

Delegate і Callback вирішують одне завдання — асинхронне сповіщення, — але різними способами. Delegate використовує протокол з іменованими методами, callback — замикання із захопленням контексту. Вибір залежить від кількості подій, складності сигнатур і архітектурних уподобань. Apple рекомендує delegate для API з багатьма подіями (UITableView — 20+ методів) і callback для разових завершень.

Коли Delegate виграє

Delegate є кращим, коли потрібно обробляти кілька різних подій від одного джерела. Наприклад, CLLocationManager сповіщає делегата про зміну місцезнаходження, помилки дозволів, вхід/вихід із геозон і зміну статусу служб. Кожна подія — окремий метод протоколу зі зрозумілим ім'ям і типізованими параметрами. Delegate також зручний для конфігурації поведінки (should, will, did методи).

Коли Callback виграє

Callback простіший для разових запитів з одним результатом. Completion handler в URLSession.dataTask займає один рядок у точці виклику проти мінімум трьох методів протоколу. Callback також природніший для функціональних ланцюжків (map, flatMap, async/await). Однак при вкладеності більше 2-3 рівнів callback перетворюється на Callback Hell, тоді як delegate завжди залишається плоским.

Вбудовані Delegate в iOS SDK

iOS SDK містить десятки вбудованих delegate-протоколів для різних підсистем. Кожен із них спроєктований для конкретного сценарію взаємодії. За даними Apple Documentation (2025), найбільш використовувані delegate — UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate та UNUserNotificationCenterDelegate. Ці протоколи містять від 3 до 30 методів із різною обов'язковістю.

UITableViewDelegate

UITableViewDelegate керує зовнішнім виглядом і поведінкою комірок таблиці. Він містить методи для обробки виділення рядків, налаштування висоти комірок, кастомних представлень header/footer і свайп-дій. Всі методи протоколу опціональні, що дозволяє реалізувати лише потрібну функціональність. За відсутності делегата таблиця працює з налаштуваннями за замовчуванням. Історично delegate поєднувався з UITableViewDataSource.

URLSessionDelegate

URLSessionDelegate забезпечує детальний контроль над HTTP-запитами. Методи делегата викликаються при отриманні відповіді сервера, даних, завершенні завантаження. Спеціалізовані підпротоколи URLSessionTaskDelegate та URLSessionDataDelegate розширюють базовий функціонал для конкретних типів завдань. Delegate потрібен для підтримки фонових завантажень, сертифікатів SSL та кастомної обробки редиректів.

DelegateМетодівПризначення
UITableViewDelegate25Зовнішній вигляд і взаємодія з таблицею
UITextFieldDelegate8Обробка введення тексту та клавіатури
CLLocationManagerDelegate12Оновлення місцезнаходження та геозони
URLSessionDelegate6Управління HTTP-сесією та сертифікатами
UNUserNotificationCenterDelegate4Обробка push-сповіщень на передньому плані

Управління пам'яттю при роботі з Delegate

Управління пам'яттю — критичний аспект роботи з delegate в iOS. ARC (Automatic Reference Counting) автоматично керує пам'яттю, але тільки при правильному використанні weak/unowned посилань. Порушення правил призводить до витоків пам'яті або передчасної деалокації. Delegate, оголошений як strong, створює retain cycle, якщо власник делегата також зберігає посилання на делегуючий обєкт.

Retain cycle через Delegate

Retain cycle виникає, коли обєкт A (власник) встановлює себе делегатом обєкта B, а B зберігає сильне посилання на delegate. Приклад: ViewController створює URLSession, встановлює self делегатом сесії, але URLSession за замовчуванням зберігає сильне посилання на delegate, якщо не вказаний delegateQueue. Рішення — завжди перевіряти документацію API на тип посилання на delegate (weak або strong) і явно обнуляти delegate в deinit.

swift
class SafeViewController: UIViewController {
    private var session: URLSession?
    private var service: NetworkService?

    override func viewDidLoad() {
        super.viewDidLoad()
        service = NetworkService()
        service?.delegate = self
    }

    deinit {
        // Обнулення delegate в deinit — best practice
        service?.delegate = nil
        session?.invalidateAndCancel()
    }
}

// URLSession з weak delegate через NSObject
class WeakDelegateSession: NSObject {
    private weak var delegate: URLSessionDelegate?

    func createSession() -> URLSession {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        return URLSession(
            configuration: .default,
            delegate: self,
            delegateQueue: queue
        )
    }
}

Безпечна перевірка delegate перед викликом

Перед викликом методу делегата необхідно перевірити, що делегат існує (не nil) і реалізує викликаний метод. Для обов'язкових методів протоколу перевірка не потрібна — компілятор гарантує реалізацію. Для опціональних методів використовуйте respond(to:) або optional chaining. Якщо delegate деаллокований, weak посилання автоматично стає nil, і виклик делегата ігнорується. Це безпечна поведінка, яка не потребує додаткової обробки.

Типові помилки при реалізації Delegate

Розробники часто допускають помилки при роботі з патерном Delegate, особливо на початковому етапі освоєння iOS. Найпоширеніші: retain cycle через strong delegate, забули викликати delegate?.method(), неправильна сигнатура методів протоколу, встановлення delegate після початку операції та багатопотокові колізії. Розглянемо кожну помилку та способи її запобігання.

Strong reference замість weak

Найкритичніша помилка — оголошення властивості delegate як strong var замість weak var. Це створює retain cycle, при якому ні делегат, ні делегуючий обєкт не можуть бути звільнені. Наслідки: витік пам'яті, уповільнення застосунку та приховані баги. Рішення: завжди використовуйте weak var для delegate, а протокол успадковуйте від AnyObject, щоб виключити використання value types як делегата.

Встановлення delegate після запуску операції

Якщо делегат встановлюється після виклику асинхронного методу, перші події можуть бути втрачені. Приклад: виклик startDownload() до присвоєння manager.delegate = self призводить до втрати callback про завершення, якщо завантаження виконується синхронно або дуже швидко. Рішення: встановлюйте delegate перед викликом асинхронного методу та документуйте порядок ініціалізації в коментарях до протоколу.

Часто задавані питання

Чому delegate оголошується як weak?

Weak запобігає retain cycle між делегатом і делегуючим обєктом. Якби посилання було strong, обєкти тримали б один одного, і ARC не зміг би їх звільнити. Weak-посилання автоматично стає nil при деалокації делегата. Це стандартна практика Cocoa Touch з моменту появи Objective-C і збережена в Swift для зворотної сумісності.

У чому різниця між delegate і dataSource?

Delegate обробляє події та керує поведінкою (висота комірок, реакція на натискання). DataSource надає дані для відображення (кількість рядків, комірки). Делегат відповідає на питання «як?», dataSource — на питання «що?». В iOS обидва реалізуються через протоколи, часто в одному контролері, але концептуально розділені.

Чи можна використовувати struct як delegate?

Ні, якщо протокол успадковує AnyObject (класовий протокол). weak-посилання доступні тільки для reference types (класів). Для value types (struct, enum) використовуйте callback-замикання або окремий клас-обгортку. Якщо ви контролюєте протокол, можна не успадковувати AnyObject, але тоді weak заборонений — вибирайте між weak-делегатом і struct-делегатом свідомо.

Що таке метод responds(to:) і навіщо він потрібен?

responds(to:) — метод NSObjectProtocol, який перевіряє, чи реалізує обєкт вказаний селектор. Використовується для перевірки опціональних методів @objc протоколу перед викликом. Без цієї перевірки виклик нереалізованого опціонального методу призведе до NSInvalidArgumentException. У Swift для протоколів із @objc optional перевірка може бути неявною через optional binding.

Delegate — це сінглтон чи ні?

Ні, delegate — це патерн делегування, а не сінглтон. На відміну від сінглтона, делегат може бути замінений у рантаймі та існує в єдиному екземплярі для кожного делегуючого обєкта. Один обєкт може бути делегатом для кількох відправників. Сінглтон — це породжуючий патерн, який гарантує єдиний екземпляр класу, що не має відношення до делегування.

Підсумки

  • Delegate — патерн, при якому обєкт делегує обробку подій іншому обєкту через протокол із типізованими методами.
  • Weak var обов'язковий для властивості delegate, щоб запобігти retain cycle та витокам пам'яті.
  • Протокол визначає обов'язкові та опціональні (@objc optional) методи делегування.
  • iOS SDK містить 15+ вбудованих delegate-протоколів: UITableViewDelegate, URLSessionDelegate, CLLocationManagerDelegate.
  • Delegate кращий за callback при 3+ різних подіях від одного джерела (CLLocationManager).
  • Асинхронний delegate використовується в URLSession для отримання даних і прогресу без блокування потоку.
  • Встановлюйте delegate перед запуском асинхронної операції та обнуляйте в deinit для безпечної роботи з пам'яттю.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

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

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