Delegate: какво е това, шаблон за делегиране и протоколи на iOS

Автор: IT Sectr Публикувано: 2026-02-17 Време за четене: 9 мин

Delegate — е шаблон за проектиране, при който един обект делегира изпълнението на задача на друг обект. В iOS шаблонът се реализира чрез Swift протоколи и @protocol Objective-C. Delegation е един от основните шаблони на Cocoa Touch, използван в UITableViewDelegate, UITextFieldDelegate и стотици други Apple API. Според Apple Developer Documentation (2025), около 70% от системните класове на UIKit използват делегати за персонализиране на поведението без наследяване.

Основни точки

  • Delegate — шаблон, при който обект А делегира изпълнението на задача на обект Б чрез протокол или интерфейс
  • Swift протокол определя набор от методи, които делегатът може или трябва да имплементира
  • Weak референция към делегата е задължителна за предотвратяване на retain cycle и изтичане на памет
  • UITableViewDelegate — най-разпространеният пример за делегат в iOS с 20+ опционални метода
  • Разлика от Observer: делегатът е отношение 1:1, докато известията работят по принципа 1:N

Какво е шаблонът Delegate?

Delegate — е шаблон за поведенческо проектиране, който позволява на обект да прехвърли част от отговорностите си на друг обект. За разлика от наследяването, където дъщерният клас презаписва методите на родителя, делегатът използва композиция: обектът-собственик съхранява референция към делегата и извиква неговите методи в определени точки.

Делегатът определя протокол — набор от методи, които делегатът може да имплементира. Методите се делят на задължителни (required) и опционални (optional). В Swift опционалните методи на протокола се маркират с ключовата дума @objc optional.

Принцип на работа на делегата

Обект А (собственик) съдържа свойството delegate — weak референция към обект Б (делегат). Когато настъпи събитие, А проверява дали Б имплементира съответния метод на протокола и го извиква. 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)
Кога да се прилагаСложни callback-и с множество методиСъбития, които интересуват мнозинаПрости затваряния с 1-2 callback-а

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) притежава таблицата, силна референция към делегата би създала цикъл: 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 за сложни callback-и с множество свързани методи и closure за прости затваряния

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

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

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