Delegate — е шаблон за проектиране, при който един обект делегира изпълнението на задача на друг обект. В iOS шаблонът се реализира чрез Swift протоколи и @protocol Objective-C. Delegation е един от основните шаблони на Cocoa Touch, използван в UITableViewDelegate, UITextFieldDelegate и стотици други Apple API. Според Apple Developer Documentation (2025), около 70% от системните класове на UIKit използват делегати за персонализиране на поведението без наследяване.
Основни точки
Delegate — е шаблон за поведенческо проектиране, който позволява на обект да прехвърли част от отговорностите си на друг обект. За разлика от наследяването, където дъщерният клас презаписва методите на родителя, делегатът използва композиция: обектът-собственик съхранява референция към делегата и извиква неговите методи в определени точки.
Делегатът определя протокол — набор от методи, които делегатът може да имплементира. Методите се делят на задължителни (required) и опционални (optional). В Swift опционалните методи на протокола се маркират с ключовата дума @objc optional.
Обект А (собственик) съдържа свойството delegate — weak референция към обект Б (делегат). Когато настъпи събитие, А проверява дали Б имплементира съответния метод на протокола и го извиква. Weak референцията е задължителна: без нея делегатът не може да бъде освободен от паметта, тъй като собственикът го държи със силна референция.
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 референцията.
Имплементацията на делегата в Swift включва три стъпки: деклариране на протокола, създаване на weak свойството delegate в собственика и имплементация на протокола в класа-делегат. Нека разгледаме примера с персонализиран UITextField с валидация.
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 | NotificationCenter | Closure |
|---|---|---|---|
| Тип връзка | 1:1 | 1:N | 1:1 |
| Свързаност | Слаба (чрез протокол) | Много слаба (ключ-низ) | Средна (улавяне на контекст) |
| Типова безопасност | Пълна | Липсва (Any?) | Пълна |
| Риск от retain cycle | Не (weak) | Не | Да (улавяне на self) |
| Кога да се прилага | Сложни callback-и с множество методи | Събития, които интересуват мнозина | Прости затваряния с 1-2 callback-а |
Delegate е оптимален, когато трябва да се предаде серия от свързани събития на един получател. NotificationCenter е по-добър за broadcast известия. Closure — за прости асинхронни операции, като completion handler в URLSession.
Objective-C използва @protocol и @optional за деклариране на делегати. За разлика от Swift, всички методи на протокола са по подразбиране опционални. Ключовата разлика — извикване на respondsToSelector: преди изпращане на съобщение до делегата, тъй като методът може да не е имплементиран.
@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.
Ако обектът-собственик надживее делегата и референцията остане, извикването на метод на делегата ще доведе до EXC_BAD_ACCESS. Weak референцията решава този проблем автоматично: след освобождаване на делегата свойството става nil. В многонишкови сценарии обаче си струва допълнително да проверявате делегата в главната нишка.
Протокол с над 20 метода нарушава принципа за разделяне на интерфейсите (ISP). UITableViewDelegate съдържа около 30 опционални метода — това е историческо изключение. В собствените си протоколи е по-добре да разделите отговорността на няколко малки протокола, всеки със своя роля.
Системните API на Apple активно използват шаблона Delegate. Нека разгледаме три ключови примера от UIKit, които се срещат във всяко iOS приложение.
| API | Протокол | Ключови методи |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
Всеки от тези протоколи имплементира различни аспекти на поведението: UITableViewDelegate управлява външния вид и реакцията на докосване, UITextFieldDelegate контролира въвеждането на текст, URLSessionDelegate обработва мрежови събития. Това демонстрира гъвкавостта на шаблона: делегатът може да бъде адаптиран към всяка област на отговорност.
Често задавани въпроси
Delegate управлява поведението и външния вид (височина на клетка, реакция на докосване). DataSource предоставя данни (брой редове, съдържание на клетки). В UITableViewDelegate и UITableViewDataSource — това са два отделни протокола, разделящи отговорността за представяне и данни.
Weak референцията предотвратява retain cycle. Собственикът (напр. UITableView) държи референция към делегата само като weak. Ако делегатът (UIViewController) притежава таблицата, силна референция към делегата би създала цикъл: ViewController → UITableView → Delegate (ViewController). Weak прекъсва този цикъл.
В SwiftUI шаблонът Delegate се използва по-рядко — заменя се от @Binding, @State и затваряния. Въпреки това, delegate все още се използва за интеграция на UIKit чрез UIViewRepresentable. Например MKMapViewDelegate и WKUIDelegate остават актуални при обвиване на UIKit компоненти в SwiftUI.
@objc optional позволява деклариране на опционални методи в Swift протокол. Това е механизъм за съвместимост с Objective-C runtime. Без @objc всички методи на Swift протокола са задължителни по подразбиране. Optional се използва в UIKit протоколи, където делегатът може да имплементира само нужните му методи.
Един обект може да има само един делегат за всяко свойство delegate. Ако трябва да уведомите множество обекти, използвайте multicast delegate, масив от делегати или NotificationCenter. Шаблонът Delegate първоначално е проектиран като отношение 1:1.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също