Delegate — este un model de proiectare în care un obiect delegă executarea unei sarcini altui obiect. În iOS, modelul este implementat prin protocoale Swift și @protocol Objective-C. Delegation este unul dintre modelele fundamentale ale Cocoa Touch, utilizat în UITableViewDelegate, UITextFieldDelegate și sute de alte API-uri Apple. Conform Apple Developer Documentation (2025), aproximativ 70% din clasele de sistem UIKit folosesc delegați pentru personalizarea comportamentului fără moștenire.
Principalele
Delegate — este un model de proiectare comportamentală care permite unui obiect să transfere o parte din responsabilitățile sale altui obiect. Spre deosebire de moștenire, unde clasa copil suprascrie metodele părintelui, delegatul folosește compoziția: obiectul-proprietar stochează o referință către delegat și apelează metodele acestuia în puncte specifice.
Delegatul definește un protocol — un set de metode pe care delegatul le poate implementa. Metodele sunt împărțite în obligatorii (required) și opționale (optional). În Swift, metodele opționale ale protocolului sunt marcate cu cuvântul cheie @objc optional.
Obiectul A (proprietarul) conține proprietatea delegate — o referință weak către obiectul B (delegatul). Când are loc un eveniment, A verifică dacă B implementează metoda corespunzătoare a protocolului și o apelează. Referința weak este obligatorie: fără ea, delegatul nu poate fi eliberat din memorie, deoarece proprietarul îl menține cu o referință strong.
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)
// încărcare asincronă
}
}Clasa DataLoader definește protocolul LoaderDelegate și apelează metodele delegatului în punctele cheie ale ciclului de viață al încărcării. AnyObject garantează că protocolul poate fi implementat doar de clase — acest lucru este necesar pentru referința weak.
Implementarea delegatului în Swift include trei pași: declararea protocolului, crearea proprietății weak delegate în proprietar și implementarea protocolului în clasa-delegat. Să analizăm pe exemplul unui UITextField personalizat cu validare.
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("Validare eșuată: intrare prea scurtă")
}
}LoginViewController implementează protocolul ValidatorDelegate și se setează ca delegat al textField. La fiecare modificare a textului, ValidatedTextField apelează validate(_:), iar dacă validarea eșuează — validatorDidFail(_:). Controller acționează ca intermediar între View și logica de validare.
Alegerea între delegat, notificări și closure-uri depinde de numărul de destinatari și gradul de cuplare al componentelor. Fiecare mecanism rezolvă problema comunicării între obiecte, dar cu compromisuri diferite.
| Caracteristică | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| Tipul legăturii | 1:1 | 1:N | 1:1 |
| Cuplare | Slabă (prin protocol) | Foarte slabă (cheie-string) | Medie (capturarea contextului) |
| Siguranța tipurilor | Completă | Lipsește (Any?) | Completă |
| Risc retain cycle | Nu (weak) | Nu | Da (capturarea self) |
| Când se aplică | Callback-uri complexe cu mai multe metode | Evenimente care interesează pe mulți | Closure-uri simple cu 1-2 callback-uri |
Delegate este optim când trebuie transmisă o serie de evenimente conexe unui singur destinatar. NotificationCenter este mai bun pentru notificări de tip broadcast. Closure — pentru operații asincrone simple, cum ar fi completion handler în URLSession.
Objective-C folosește @protocol și @optional pentru declararea delegaților. Spre deosebire de Swift, toate metodele protocolului sunt implicit opționale. Diferența cheie — apelul respondsToSelector: înainte de a trimite un mesaj delegatului, deoarece metoda poate să nu fie implementată.
@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];
}
// încărcare asincronă a imaginii
}
@endDiferența cheie în Objective-C: înainte de apelarea unei metode opționale este necesară verificarea respondsToSelector:. În Swift, metodele opționale ale protocolului elimină această verificare — optional chaining (?.) gestionează automat cazul lipsei implementării.
Erorile de utilizare a delegaților duc la scurgeri de memorie, crash-uri ale aplicației și bug-uri neevidente. Să analizăm cinci dintre cele mai frecvente probleme.
Retain cycle — cea mai frecventă eroare. Dacă proprietatea delegate este declarată ca strong, iar delegatul la rândul său deține obiectul-proprietar, se formează un ciclu de reținere. Ambele obiecte nu vor fi niciodată eliberate din memorie. Soluție: declarați întotdeauna delegatul ca weak var în Swift sau @property (weak) în Objective-C.
Dacă obiectul-proprietar supraviețuiește delegatului, iar referința rămâne, apelarea metodei delegatului va duce la EXC_BAD_ACCESS. Referința weak rezolvă această problemă automat: după eliberarea delegatului, proprietatea devine nil. Totuși, în scenarii multi-thread, este recomandat să verificați delegatul în firul principal.
Un protocol cu peste 20 de metode încalcă principiul separării interfețelor (ISP). UITableViewDelegate conține aproximativ 30 de metode opționale — aceasta este o excepție istorică. În protocoalele proprii, este mai bine să împărțiți responsabilitatea în mai multe protocoale mici, fiecare cu propriul rol.
API-urile de sistem Apple utilizează activ modelul Delegate. Să analizăm trei exemple cheie din UIKit care apar în fiecare aplicație iOS.
| API | Protocol | Metode cheie |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
Fiecare dintre aceste protocoale implementează aspecte diferite ale comportamentului: UITableViewDelegate gestionează aspectul și reacția la atingeri, UITextFieldDelegate controlează introducerea textului, URLSessionDelegate procesează evenimentele de rețea. Aceasta demonstrează flexibilitatea modelului: delegatul poate fi adaptat oricărui domeniu de responsabilitate.
Întrebări frecvente
Delegate gestionează comportamentul și aspectul (înălțimea celulei, reacția la atingere). DataSource furnizează date (numărul de rânduri, conținutul celulelor). În UITableViewDelegate și UITableViewDataSource — sunt două protocoale separate, care împart responsabilitatea pentru prezentare și date.
Referința weak previne retain cycle. Proprietarul (de exemplu, UITableView) menține referința către delegat doar ca weak. Dacă delegatul (UIViewController) deține tabelul, o referință strong către delegat ar crea un ciclu: ViewController → UITableView → Delegate (ViewController). Weak întrerupe acest ciclu.
În SwiftUI, modelul Delegate este folosit mai rar — este înlocuit de @Binding, @State și closure-uri. Totuși, delegate este încă utilizat pentru integrarea UIKit prin UIViewRepresentable. De exemplu, MKMapViewDelegate și WKUIDelegate rămân relevante la împachetarea componentelor UIKit în SwiftUI.
@objc optional permite declararea metodelor opționale într-un protocol Swift. Este un mecanism de compatibilitate cu Objective-C runtime. Fără @objc, toate metodele protocolului Swift sunt implicit obligatorii. Optional este utilizat în protocoalele UIKit, unde delegatul poate implementa doar metodele de care are nevoie.
Un obiect poate avea doar un delegat pentru fiecare proprietate delegate. Dacă trebuie să notificați mai multe obiecte, utilizați multicast delegate, un array de delegați sau NotificationCenter. Modelul Delegate este conceput inițial ca o relație 1:1.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și