Delegate — це патерн проєктування, при якому один обєкт делегує виконання завдань іншому обєкту через протокол із заздалегідь визначеними методами. У розробці iOS Delegate є одним із фундаментальних патернів Cocoa Touch, який використовується для асинхронного сповіщення без прямого зв'язку між відправником і отримувачем. За даними Apple Documentation (2025), делегування застосовується в Foundation і UIKit для обробки подій таблиць, мережевих запитів і управління місцезнаходженням. Патерн забезпечує слабку зв'язаність компонентів і повторне використання коду.
Головне
Delegate (делегат) — це обєкт, який реалізує певний протокол і отримує сповіщення про події іншого обєкта. Патерн Delegation є альтернативою успадкуванню: замість створення підкласу для перевизначення методів, обєкт делегує обробку подій зовнішньому обєкту. В iOS делегування реалізовано через протоколи Swift з обов'язковими та опціональними методами. Властивість delegate завжди оголошується як weak var, щоб уникнути циклічних посилань між обєктами.
Протокол delegate визначає контракт взаємодії між обєктами. Обов'язкові методи повинні бути реалізовані делегатом, інакше код не скомпілюється. Опціональні методи позначаються атрибутом @objc optional і дозволяють делегату реагувати лише на потрібні події. Імена методів слідують конвенції: перший параметр — обєкт-відправник, другий — дані події. Наприклад, tableView(_:didSelectRowAt:) вказує, що відправник — UITableView, а дані — індекс вибраного рядка.
// Протокол 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 обов'язково оголошується як weak var для запобігання retain cycle. Якби посилання було сильним, делегат і делегуючий обєкт тримали б один одного, і ARC не зміг би звільнити їх пам'ять. Протоколи delegate успадковують AnyObject (тільки для класів), що дозволяє використовувати weak. Структури та переліки не можуть бути делегатами через семантику значень. Альтернатива для value types — callback closures.
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 працює за принципом «один-до-одного»: один обєкт-відправник може мати лише одного делегата в конкретний момент часу. Коли відбувається подія, відправник перевіряє, чи встановлений delegate, і викликає відповідний метод протоколу. Перевага перед прямим викликом — відправник не знає тип делегата, лише те, що він відповідає протоколу. Це дотримується принципу інверсії залежностей (DIP) з SOLID.
Делегат призначається через присвоєння: someObject.delegate = self. При деалокації делегата властивість автоматично стає nil через weak semantics. Перед викликом методу делегат перевіряється через optional chaining: delegate?.method(). Якщо delegate nil — виклик ігнорується без крашу. Для опціональних методів протоколу використовується додаткова перевірка: delegate?.responds(to: #selector(...)), хоча в Swift ця перевірка зазвичай неявна через optional method declaration.
У багатопотоковому середовищі delegate використовується для асинхронного повернення результату. URLSession надає URLSessionDelegate з методами, які викликаються при отриманні даних, таймауті або помилці аутентифікації. Методи delegate виконуються у фоновій черзі URLSession, тому потрібна диспетчеризація на main queue для UI-оновлень. Асинхронний delegate не блокує викликаючий потік, дозволяючи продовжити виконання інших завдань.
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 і Callback вирішують одне завдання — асинхронне сповіщення, — але різними способами. Delegate використовує протокол з іменованими методами, callback — замикання із захопленням контексту. Вибір залежить від кількості подій, складності сигнатур і архітектурних уподобань. Apple рекомендує delegate для API з багатьма подіями (UITableView — 20+ методів) і callback для разових завершень.
Delegate є кращим, коли потрібно обробляти кілька різних подій від одного джерела. Наприклад, CLLocationManager сповіщає делегата про зміну місцезнаходження, помилки дозволів, вхід/вихід із геозон і зміну статусу служб. Кожна подія — окремий метод протоколу зі зрозумілим ім'ям і типізованими параметрами. Delegate також зручний для конфігурації поведінки (should, will, did методи).
Callback простіший для разових запитів з одним результатом. Completion handler в URLSession.dataTask займає один рядок у точці виклику проти мінімум трьох методів протоколу. Callback також природніший для функціональних ланцюжків (map, flatMap, async/await). Однак при вкладеності більше 2-3 рівнів callback перетворюється на Callback Hell, тоді як delegate завжди залишається плоским.
iOS SDK містить десятки вбудованих delegate-протоколів для різних підсистем. Кожен із них спроєктований для конкретного сценарію взаємодії. За даними Apple Documentation (2025), найбільш використовувані delegate — UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate та UNUserNotificationCenterDelegate. Ці протоколи містять від 3 до 30 методів із різною обов'язковістю.
UITableViewDelegate керує зовнішнім виглядом і поведінкою комірок таблиці. Він містить методи для обробки виділення рядків, налаштування висоти комірок, кастомних представлень header/footer і свайп-дій. Всі методи протоколу опціональні, що дозволяє реалізувати лише потрібну функціональність. За відсутності делегата таблиця працює з налаштуваннями за замовчуванням. Історично delegate поєднувався з UITableViewDataSource.
URLSessionDelegate забезпечує детальний контроль над HTTP-запитами. Методи делегата викликаються при отриманні відповіді сервера, даних, завершенні завантаження. Спеціалізовані підпротоколи URLSessionTaskDelegate та URLSessionDataDelegate розширюють базовий функціонал для конкретних типів завдань. Delegate потрібен для підтримки фонових завантажень, сертифікатів SSL та кастомної обробки редиректів.
| Delegate | Методів | Призначення |
|---|---|---|
| UITableViewDelegate | 25 | Зовнішній вигляд і взаємодія з таблицею |
| UITextFieldDelegate | 8 | Обробка введення тексту та клавіатури |
| CLLocationManagerDelegate | 12 | Оновлення місцезнаходження та геозони |
| URLSessionDelegate | 6 | Управління HTTP-сесією та сертифікатами |
| UNUserNotificationCenterDelegate | 4 | Обробка push-сповіщень на передньому плані |
Управління пам'яттю — критичний аспект роботи з delegate в iOS. ARC (Automatic Reference Counting) автоматично керує пам'яттю, але тільки при правильному використанні weak/unowned посилань. Порушення правил призводить до витоків пам'яті або передчасної деалокації. Delegate, оголошений як strong, створює retain cycle, якщо власник делегата також зберігає посилання на делегуючий обєкт.
Retain cycle виникає, коли обєкт A (власник) встановлює себе делегатом обєкта B, а B зберігає сильне посилання на delegate. Приклад: ViewController створює URLSession, встановлює self делегатом сесії, але URLSession за замовчуванням зберігає сильне посилання на delegate, якщо не вказаний delegateQueue. Рішення — завжди перевіряти документацію API на тип посилання на delegate (weak або strong) і явно обнуляти delegate в deinit.
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
)
}
}
Перед викликом методу делегата необхідно перевірити, що делегат існує (не nil) і реалізує викликаний метод. Для обов'язкових методів протоколу перевірка не потрібна — компілятор гарантує реалізацію. Для опціональних методів використовуйте respond(to:) або optional chaining. Якщо delegate деаллокований, weak посилання автоматично стає nil, і виклик делегата ігнорується. Це безпечна поведінка, яка не потребує додаткової обробки.
Розробники часто допускають помилки при роботі з патерном Delegate, особливо на початковому етапі освоєння iOS. Найпоширеніші: retain cycle через strong delegate, забули викликати delegate?.method(), неправильна сигнатура методів протоколу, встановлення delegate після початку операції та багатопотокові колізії. Розглянемо кожну помилку та способи її запобігання.
Найкритичніша помилка — оголошення властивості delegate як strong var замість weak var. Це створює retain cycle, при якому ні делегат, ні делегуючий обєкт не можуть бути звільнені. Наслідки: витік пам'яті, уповільнення застосунку та приховані баги. Рішення: завжди використовуйте weak var для delegate, а протокол успадковуйте від AnyObject, щоб виключити використання value types як делегата.
Якщо делегат встановлюється після виклику асинхронного методу, перші події можуть бути втрачені. Приклад: виклик startDownload() до присвоєння manager.delegate = self призводить до втрати callback про завершення, якщо завантаження виконується синхронно або дуже швидко. Рішення: встановлюйте delegate перед викликом асинхронного методу та документуйте порядок ініціалізації в коментарях до протоколу.
Часто задавані питання
Weak запобігає retain cycle між делегатом і делегуючим обєктом. Якби посилання було strong, обєкти тримали б один одного, і ARC не зміг би їх звільнити. Weak-посилання автоматично стає nil при деалокації делегата. Це стандартна практика Cocoa Touch з моменту появи Objective-C і збережена в Swift для зворотної сумісності.
Delegate обробляє події та керує поведінкою (висота комірок, реакція на натискання). DataSource надає дані для відображення (кількість рядків, комірки). Делегат відповідає на питання «як?», dataSource — на питання «що?». В iOS обидва реалізуються через протоколи, часто в одному контролері, але концептуально розділені.
Ні, якщо протокол успадковує AnyObject (класовий протокол). weak-посилання доступні тільки для reference types (класів). Для value types (struct, enum) використовуйте callback-замикання або окремий клас-обгортку. Якщо ви контролюєте протокол, можна не успадковувати AnyObject, але тоді weak заборонений — вибирайте між weak-делегатом і struct-делегатом свідомо.
responds(to:) — метод NSObjectProtocol, який перевіряє, чи реалізує обєкт вказаний селектор. Використовується для перевірки опціональних методів @objc протоколу перед викликом. Без цієї перевірки виклик нереалізованого опціонального методу призведе до NSInvalidArgumentException. У Swift для протоколів із @objc optional перевірка може бути неявною через optional binding.
Ні, delegate — це патерн делегування, а не сінглтон. На відміну від сінглтона, делегат може бути замінений у рантаймі та існує в єдиному екземплярі для кожного делегуючого обєкта. Один обєкт може бути делегатом для кількох відправників. Сінглтон — це породжуючий патерн, який гарантує єдиний екземпляр класу, що не має відношення до делегування.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також