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. Структурите и enum не могат да бъдат делегати поради семантиката на стойностите. Алтернатива за 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 работи на принципа „един-към-един“: един обект-изпращач може да има само един делегат в даден момент. Когато възникне събитие, изпращачът проверява дали делегатът е зададен и извиква съответния метод на протокола. Предимство пред директното извикване — изпращачът не знае типа на делегата, само че той отговаря на протокола. Това спазва принципа на инверсия на зависимостите (DIP) от SOLID.

Жизнен цикъл на делегата

Делегатът се задава чрез присвояване: someObject.delegate = self. При деалокация на делегата свойството автоматично става nil поради weak семантиката. Преди извикване на метода на delegate се проверява чрез optional chaining: delegate?.method(). Ако delegate е nil — извикването се игнорира без crash. За незадължителните методи на протокола се използва допълнителна проверка: delegate?.responds(to: #selector(...)), въпреки че в Swift тази проверка обикновено е имплицитна чрез декларация на незадължителен метод.

Асинхронно известяване чрез 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 — closure с улавяне на контекст. Изборът зависи от броя събития, сложността на подписите и архитектурните предпочитания. 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 винаги остава плосък.

Вградени делегати в iOS SDK

iOS SDK съдържа десетки вградени delegate протоколи за различни подсистеми. Всеки от тях е проектиран за конкретен сценарий на взаимодействие. Според Apple Documentation (2025), най-често използваните delegate са UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate и UNUserNotificationCenterDelegate. Тези протоколи съдържат от 3 до 30 метода с различна степен на задължителност.

UITableViewDelegate

UITableViewDelegate управлява външния вид и поведението на клетките на таблицата. Съдържа методи за обработка на избор на редове, настройка на височина на клетки, персонализирани header/footer изгледи и swipe действия. Всички методи на протокола са незадължителни, което позволява имплементиране само на необходимата функционалност. При липса на делегат таблицата работи с настройки по подразбиране. Исторически 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. Ако делегатът е бил деалокиран, 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 след стартиране на операция

Ако delegate се зададе след извикване на асинхронен метод, първите събития могат да бъдат загубени. Пример: извикване на startDownload() преди присвояване на manager.delegate = self води до загуба на callback за завършване, ако изтеглянето се изпълнява синхронно или много бързо. Решение: задайте delegate преди извикване на асинхронния метод и документирайте реда на инициализация в коментарите на протокола.

Често задавани въпроси

Защо delegate се декларира като weak?

Weak предотвратява retain cycle между делегата и делегиращия обект. Ако референцията беше силна, обектите биха се задържали взаимно и ARC не би могла да ги освободи. Weak-референцията автоматично става nil при деалокация на делегата. Това е стандартна практика на Cocoa Touch от появата на Objective-C и е запазена в Swift за обратна съвместимост.

Каква е разликата между delegate и dataSource?

Delegate обработва събития и управлява поведение (височина на клетки, реакция на кликвания). DataSource предоставя данни за показване (брой редове, клетки). Delegate отговаря на въпроса „как?”, dataSource — на въпроса „какво?”. В iOS и двата се имплементират чрез протоколи, често в един и същ контролер, но концептуално са разделени.

Може ли struct да се използва като delegate?

Не, ако протоколът наследява AnyObject (класов протокол). Weak-референции са достъпни само за reference types (класове). За value types (struct, enum) използвайте callback closures или отделен wrapper клас. Ако контролирате протокола, можете да не наследявате AnyObject, но тогава weak е забранен — избирайте съзнателно между weak-delegate и struct-делегат.

Какво е методът responds(to:) и за какво служи?

responds(to:) — метод на NSObjectProtocol, който проверява дали обектът имплементира посочения selector. Използва се за проверка на незадължителни @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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също