Delegate è un pattern di progettazione in cui un oggetto delega l'esecuzione di un compito a un altro oggetto. In iOS, il pattern è implementato tramite i protocolli Swift e @protocol Objective-C. La delegazione è uno dei pattern fondamentali di Cocoa Touch, utilizzato in UITableViewDelegate, UITextFieldDelegate e centinaia di altre API Apple. Secondo Apple Developer Documentation (2025), circa il 70% delle classi di sistema UIKit utilizza delegati per personalizzare il comportamento senza ereditarietà.
Punti chiave
Delegate è un pattern di progettazione comportamentale che permette a un oggetto di delegare parte delle sue responsabilità a un altro oggetto. A differenza dell'ereditarietà, dove una classe figlia sovrascrive i metodi del genitore, il delegato utilizza la composizione: l'oggetto proprietario mantiene un riferimento al delegato e chiama i suoi metodi in punti specifici.
Il delegato definisce un protocollo — un insieme di metodi che il delegato può implementare. I metodi sono divisi in obbligatori (required) e opzionali (optional). In Swift, i metodi opzionali del protocollo sono contrassegnati con la parola chiave @objc optional.
L'oggetto A (proprietario) contiene una proprietà delegate — un riferimento debole all'oggetto B (delegato). Quando si verifica un evento, A controlla se B implementa il metodo del protocollo corrispondente e lo chiama. Il riferimento debole è obbligatorio: senza di esso, il delegato non può essere rilasciato dalla memoria perché il proprietario lo trattiene con un riferimento forte.
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)
// caricamento asincrono
}
}La classe DataLoader definisce il protocollo LoaderDelegate e chiama i metodi del delegato nei punti chiave del ciclo di caricamento. AnyObject garantisce che il protocollo possa essere implementato solo da classi — questo è necessario per il riferimento debole.
L'implementazione del delegato in Swift include tre passaggi: dichiarazione del protocollo, creazione di una proprietà delegate debole nel proprietario e implementazione del protocollo nella classe delegata. Consideriamo un esempio di UITextField personalizzato con validazione.
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("Convalida fallita: input troppo breve")
}
}LoginViewController implementa il protocollo ValidatorDelegate e si assegna come delegato di textField. Ad ogni modifica del testo, ValidatedTextField chiama validate(_:), e se la validazione fallisce — validatorDidFail(_:). Il controller funge da intermediario tra la Vista e la logica di validazione.
La scelta tra delegato, notifiche e closures dipende dal numero di destinatari e dall'accoppiamento dei componenti. Ogni meccanismo risolve il problema della comunicazione tra oggetti, ma con diversi compromessi.
| Caratteristica | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| Tipo di comunicazione | 1:1 | 1:N | 1:1 |
| Accoppiamento | Debole (tramite protocollo) | Molto debole (chiave stringa) | Medio (cattura del contesto) |
| Sicurezza dei tipi | Completa | Nessuna (Any?) | Completa |
| Rischio retain cycle | No (weak) | No | Sì (cattura di self) |
| Quando usare | Callback complessi con più metodi | Eventi che interessano molti | Closure semplici con 1-2 callback |
Delegate è ottimale quando è necessario passare una serie di eventi correlati a un singolo destinatario. NotificationCenter è migliore per notifiche broadcast. Closure è per operazioni asincrone semplici, come i completion handler in URLSession.
Objective-C utilizza @protocol e @optional per dichiarare i delegati. A differenza di Swift, tutti i metodi del protocollo sono opzionali per impostazione predefinita. La differenza principale è la chiamata respondsToSelector: prima di inviare un messaggio al delegato, poiché il metodo potrebbe non essere implementato.
@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];
}
// caricamento asincrono dell'immagine
}
@endLa differenza principale in Objective-C: prima di chiamare un metodo opzionale, è necessario un controllo respondsToSelector:. In Swift, i metodi opzionali del protocollo eliminano questo controllo — l'optional chaining (?.) gestisce automaticamente l'assenza di implementazione.
Gli errori nell'uso dei delegati portano a perdite di memoria, crash dell'applicazione e bug non ovvi. Esaminiamo i cinque problemi più comuni.
Il ciclo di ritenzione è l'errore più comune. Se la proprietà delegate è dichiarata come strong e il delegato a sua volta possiede l'oggetto proprietario, si forma un ciclo di ritenzione. Entrambi gli oggetti non verranno mai rilasciati dalla memoria. Soluzione: dichiarare sempre il delegato come weak var in Swift o @property (weak) in Objective-C.
Se l'oggetto proprietario sopravvive al delegato e il riferimento rimane, chiamare il metodo del delegato provocherà EXC_BAD_ACCESS. Il riferimento debole risolve automaticamente questo problema: dopo il rilascio del delegato, la proprietà diventa nil. Tuttavia, in scenari multithread, è necessario verificare ulteriormente il delegato nel thread principale.
Un protocollo con oltre 20 metodi viola il Principio di Segregazione delle Interfacce (ISP). UITableViewDelegate contiene circa 30 metodi opzionali — questa è un'eccezione storica. Nei tuoi protocolli, è meglio suddividere la responsabilità in più protocolli più piccoli, ciascuno con il proprio ruolo.
Le API di sistema Apple utilizzano attivamente il pattern Delegate. Esaminiamo tre esempi chiave di UIKit che appaiono in ogni applicazione iOS.
| API | Protocollo | Metodi chiave |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
Ciascuno di questi protocolli implementa diversi aspetti del comportamento: UITableViewDelegate gestisce l'aspetto e la risposta al tocco, UITextFieldDelegate controlla l'input di testo, URLSessionDelegate gestisce gli eventi di rete. Questo dimostra la flessibilità del pattern: il delegato può essere adattato a qualsiasi area di responsabilità.
Domande frequenti
Delegate gestisce il comportamento e l'aspetto (altezza della cella, risposta al tocco). DataSource fornisce i dati (numero di righe, contenuto delle celle). In UITableViewDelegate e UITableViewDataSource — sono due protocolli separati che dividono la responsabilità tra presentazione e dati.
Il riferimento debole previene i cicli di ritenzione. Il proprietario (ad esempio, UITableView) mantiene un riferimento al delegato solo come weak. Se il delegato (UIViewController) possiede la tabella, un riferimento forte al delegato creerebbe un ciclo: ViewController → UITableView → Delegate (ViewController). Weak rompe questo ciclo.
In SwiftUI, il pattern Delegate è usato meno frequentemente — viene sostituito da @Binding, @State e closures. Tuttavia, il delegato è ancora utilizzato per l'integrazione UIKit tramite UIViewRepresentable. Ad esempio, MKMapViewDelegate e WKUIDelegate rimangono rilevanti quando si incapsulano componenti UIKit in SwiftUI.
@objc optional permette di dichiarare metodi opzionali in un protocollo Swift. Questo è un meccanismo di compatibilità con il runtime di Objective-C. Senza @objc, tutti i metodi del protocollo Swift sono obbligatori per impostazione predefinita. Optional viene utilizzato nei protocolli UIKit dove il delegato può implementare solo i metodi di cui ha bisogno.
Un oggetto può avere un solo delegato per ogni proprietà delegate. Se è necessario notificare più oggetti, utilizza un delegato multicast, un array di delegati o NotificationCenter. Il pattern Delegate è stato originariamente progettato come una relazione 1:1.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche