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. Структурите и enum не могат да бъдат делегати поради семантиката на стойностите. Алтернатива за 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 работи на принципа „един-към-един“: един обект-изпращач може да има само един делегат в даден момент. Когато възникне събитие, изпращачът проверява дали делегатът е зададен и извиква съответния метод на протокола. Предимство пред директното извикване — изпращачът не знае типа на делегата, само че той отговаря на протокола. Това спазва принципа на инверсия на зависимостите (DIP) от SOLID.
Делегатът се задава чрез присвояване: someObject.delegate = self. При деалокация на делегата свойството автоматично става nil поради weak семантиката. Преди извикване на метода на delegate се проверява чрез optional chaining: delegate?.method(). Ако delegate е nil — извикването се игнорира без crash. За незадължителните методи на протокола се използва допълнителна проверка: delegate?.responds(to: #selector(...)), въпреки че в Swift тази проверка обикновено е имплицитна чрез декларация на незадължителен метод.
В многопоточна среда 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 — closure с улавяне на контекст. Изборът зависи от броя събития, сложността на подписите и архитектурните предпочитания. 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 изгледи и swipe действия. Всички методи на протокола са незадължителни, което позволява имплементиране само на необходимата функционалност. При липса на делегат таблицата работи с настройки по подразбиране. Исторически 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. Ако делегатът е бил деалокиран, weak референцията автоматично става nil и извикването на делегата се игнорира. Това е безопасно поведение, което не изисква допълнителна обработка.
Разработчиците често допускат грешки при работа с модела Delegate, особено в началните етапи на изучаване на iOS. Най-честите: retain cycle поради strong delegate, забравяне да се извика delegate?.method(), неправилен подпис на методите на протокола, задаване на delegate след започване на операция и многопоточни сблъсъци. Нека разгледаме всяка грешка и начините за предотвратяването й.
Най-критичната грешка — деклариране на свойството delegate като strong var вместо weak var. Това създава retain cycle, при който нито делегатът, нито делегиращият обект могат да бъдат освободени. Последици: изтичане на памет, забавяне на приложението и скрити бъгове. Решение: винаги използвайте weak var за delegate, а протоколът наследявайте от AnyObject, за да изключите използването на value types като делегат.
Ако delegate се зададе след извикване на асинхронен метод, първите събития могат да бъдат загубени. Пример: извикване на startDownload() преди присвояване на manager.delegate = self води до загуба на callback за завършване, ако изтеглянето се изпълнява синхронно или много бързо. Решение: задайте delegate преди извикване на асинхронния метод и документирайте реда на инициализация в коментарите на протокола.
Често задавани въпроси
Weak предотвратява retain cycle между делегата и делегиращия обект. Ако референцията беше силна, обектите биха се задържали взаимно и ARC не би могла да ги освободи. Weak-референцията автоматично става nil при деалокация на делегата. Това е стандартна практика на Cocoa Touch от появата на Objective-C и е запазена в Swift за обратна съвместимост.
Delegate обработва събития и управлява поведение (височина на клетки, реакция на кликвания). DataSource предоставя данни за показване (брой редове, клетки). Delegate отговаря на въпроса „как?”, dataSource — на въпроса „какво?”. В iOS и двата се имплементират чрез протоколи, често в един и същ контролер, но концептуално са разделени.
Не, ако протоколът наследява AnyObject (класов протокол). Weak-референции са достъпни само за reference types (класове). За value types (struct, enum) използвайте callback closures или отделен wrapper клас. Ако контролирате протокола, можете да не наследявате AnyObject, но тогава weak е забранен — избирайте съзнателно между weak-delegate и struct-делегат.
responds(to:) — метод на NSObjectProtocol, който проверява дали обектът имплементира посочения selector. Използва се за проверка на незадължителни @objc методи на протокола преди извикване. Без тази проверка извикването на неимплементиран незадължителен метод ще доведе до NSInvalidArgumentException. В Swift за протоколи с @objc optional проверката може да бъде имплицитна чрез optional binding.
Не, delegate е модел на делегиране, а не сингълтън. За разлика от сингълтъна, делегатът може да бъде заменен по време на изпълнение и съществува в един екземпляр за всяка делегираща същност. Един обект може да бъде делегат за множество изпращачи. Сингълтънът е създаващ модел, който гарантира единствен екземпляр на клас и няма нищо общо с делегирането.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също