Delegate è un pattern di progettazione in cui un oggetto delega l'esecuzione di compiti a un altro oggetto attraverso un protocollo con metodi predefiniti. Nello sviluppo iOS, Delegate è uno dei pattern fondamentali di Cocoa Touch, utilizzato per la notifica asincrona senza accoppiamento diretto tra mittente e destinatario. Secondo Apple Documentation (2025), la delega viene utilizzata in Foundation e UIKit per gestire eventi di tabelle, richieste di rete e gestione della posizione. Il pattern garantisce un basso accoppiamento dei componenti e il riutilizzo del codice.
Punti chiave
Delegate (delegato) è un oggetto che implementa un protocollo specifico e riceve notifiche sugli eventi di un altro oggetto. Il pattern di delega è un'alternativa all'ereditarietà: invece di creare una sottoclasse per sovrascrivere i metodi, un oggetto delega la gestione degli eventi a un oggetto esterno. In iOS, la delega è implementata attraverso protocolli Swift con metodi obbligatori e opzionali. La proprietà delegate è sempre dichiarata come weak var per evitare riferimenti circolari tra oggetti.
Un protocollo delegate definisce il contratto di interazione tra oggetti. I metodi obbligatori devono essere implementati dal delegato, altrimenti il codice non verrà compilato. I metodi opzionali sono contrassegnati con l'attributo @objc optional e consentono al delegato di rispondere solo agli eventi rilevanti. I nomi dei metodi seguono una convenzione: il primo parametro è l'oggetto mittente, il secondo sono i dati dell'evento. Ad esempio, tableView(_:didSelectRowAt:) indica che il mittente è UITableView e i dati sono l'indice della riga selezionata.
// Protocollo 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)
}
// Classe che usa 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()
}
}
La proprietà delegate deve essere dichiarata come weak var per prevenire retain cycle. Se il riferimento fosse forte, il delegato e l'oggetto delegante si terrebbero reciprocamente e ARC non potrebbe liberare la loro memoria. I protocolli delegate ereditano AnyObject (solo classi), il che consente di utilizzare weak. Le strutture e le enumerazioni non possono essere delegate a causa della semantica dei valori. Un'alternativa per i tipi valore sono le callback closures.
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — nessun retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
Il pattern Delegate funziona uno-a-uno: un oggetto mittente può avere un solo delegate in un dato momento. Quando si verifica un evento, il mittente controlla se il delegate è impostato e chiama il metodo corrispondente del protocollo. Il vantaggio rispetto alle chiamate dirette è che il mittente non conosce il tipo del delegate, solo che è conforme al protocollo. Questo è conforme al Principio di Inversione delle Dipendenze (DIP) di SOLID.
Il delegato viene assegnato tramite assegnazione: someObject.delegate = self. Quando il delegato viene deallocato, la proprietà diventa automaticamente nil a causa della semantica weak. Prima di chiamare un metodo del delegato, il delegate viene verificato tramite optional chaining: delegate?.method(). Se delegate è nil, la chiamata viene ignorata senza crash. Per i metodi opzionali del protocollo, viene utilizzato un controllo aggiuntivo: delegate?.responds(to: #selector(...)), anche se in Swift questo controllo è solitamente implicito attraverso la dichiarazione di metodo opzionale.
In un ambiente multithread, il delegate viene utilizzato per la restituzione asincrona dei risultati. URLSession fornisce URLSessionDelegate con metodi chiamati quando i dati vengono ricevuti, in caso di timeout o errore di autenticazione. I metodi del delegate vengono eseguiti nella coda in background di URLSession, quindi è necessario il dispatch sulla coda principale per gli aggiornamenti dell'interfaccia utente. Il delegate asincrono non blocca il thread chiamante, consentendo ad altre attività di continuare.
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 e Callback risolvono lo stesso problema — la notifica asincrona — ma in modi diversi. Delegate utilizza un protocollo con metodi nominati, Callback utilizza una closure con cattura del contesto. La scelta dipende dal numero di eventi, dalla complessità delle firme e dalle preferenze architetturali. Apple raccomanda Delegate per API con più eventi (UITableView — 20+ metodi) e Callback per completamenti una tantum.
Delegate è preferibile quando si gestiscono più eventi diversi da un'unica fonte. Ad esempio, CLLocationManager notifica al suo delegato cambiamenti di posizione, errori di autorizzazione, entrata/uscita da geofence e cambiamenti di stato del servizio. Ogni evento è un metodo di protocollo separato con un nome chiaro e parametri tipizzati. Delegate è anche comodo per la configurazione del comportamento (metodi should, will, did).
Callback è più semplice per richieste una tantum con un singolo risultato. Completion handler in URLSession.dataTask occupa una riga nel punto di chiamata contro almeno tre metodi del protocollo. Callback è anche più naturale per catene funzionali (map, flatMap, async/await). Tuttavia, con annidamento oltre 2-3 livelli, Callback diventa Callback Hell, mentre Delegate rimane sempre piatto.
L'SDK iOS contiene dozzine di protocolli delegate integrati per vari sottosistemi. Ciascuno è progettato per uno scenario di interazione specifico. Secondo Apple Documentation (2025), i delegate più utilizzati sono UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate e UNUserNotificationCenterDelegate. Questi protocolli contengono da 3 a 30 metodi con diversi livelli di obbligatorietà.
UITableViewDelegate gestisce l'aspetto e il comportamento delle celle della tabella. Contiene metodi per gestire la selezione delle righe, configurare l'altezza delle celle, viste personalizzate di intestazione/piè di pagina e azioni di scorrimento. Tutti i metodi del protocollo sono opzionali, consentendo di implementare solo la funzionalità necessaria. Senza delegate, la tabella funziona con le impostazioni predefinite. Storicamente, il delegate era combinato con UITableViewDataSource.
URLSessionDelegate fornisce un controllo dettagliato sulle richieste HTTP. I metodi del delegate vengono chiamati quando viene ricevuta una risposta dal server, arrivano i dati o il download viene completato. I sottoprotocolli specializzati URLSessionTaskDelegate e URLSessionDataDelegate estendono le funzionalità di base per tipi di attività specifici. Il delegate è necessario per supportare download in background, certificati SSL e gestione personalizzata dei reindirizzamenti.
| Delegate | Metodi | Scopo |
|---|---|---|
| UITableViewDelegate | 25 | Aspetto e interazione della tabella |
| UITextFieldDelegate | 8 | Gestione dell'input di testo e della tastiera |
| CLLocationManagerDelegate | 12 | Aggiornamenti della posizione e geofence |
| URLSessionDelegate | 6 | Gestione della sessione HTTP e dei certificati |
| UNUserNotificationCenterDelegate | 4 | Gestione delle notifiche push in primo piano |
La gestione della memoria è un aspetto critico del lavoro con delegate in iOS. ARC (Automatic Reference Counting) gestisce automaticamente la memoria, ma solo con il corretto uso dei riferimenti weak/unowned. Violare le regole porta a perdite di memoria o deallocazione prematura. Un delegate dichiarato come strong crea un retain cycle se il proprietario del delegate mantiene anche un riferimento all'oggetto delegante.
Un retain cycle si verifica quando l'oggetto A (proprietario) si imposta come delegate dell'oggetto B, e B mantiene un riferimento forte al delegate. Esempio: ViewController crea URLSession, si imposta come delegate della sessione, ma URLSession per impostazione predefinita mantiene un riferimento forte al delegate se delegateQueue non è specificato. La soluzione è controllare sempre la documentazione dell'API per il tipo di riferimento del delegate (weak o strong) e impostare esplicitamente il delegate a nil in deinit.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// Impostare delegate a nil in deinit — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// URLSession con weak delegate tramite 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
)
}
}
Prima di chiamare un metodo del delegate, è necessario verificare che il delegate esista (non sia nil) e implementi il metodo chiamato. Per i metodi obbligatori del protocollo, non è necessaria alcuna verifica — il compilatore garantisce l'implementazione. Per i metodi opzionali, utilizzare respond(to:) o optional chaining. Se il delegate viene deallocato, il riferimento weak diventa automaticamente nil e la chiamata al delegate viene ignorata. Questo comportamento è sicuro e non richiede gestione aggiuntiva.
Gli sviluppatori spesso commettono errori quando lavorano con il pattern Delegate, specialmente nelle fasi iniziali dell'apprendimento di iOS. I più comuni includono: retain cycle a causa di delegate strong, dimenticare di chiamare delegate?.method(), firma errata dei metodi del protocollo, impostazione del delegate dopo l'avvio di un'operazione e collisioni di multithreading. Esaminiamo ogni errore e come prevenirlo.
L'errore più critico è dichiarare la proprietà delegate come strong var invece di weak var. Questo crea un retain cycle in cui né il delegate né l'oggetto delegante possono essere liberati. Conseguenze: perdite di memoria, rallentamento dell'app e bug nascosti. Soluzione: utilizzare sempre weak var per delegate e far ereditare il protocollo da AnyObject per impedire l'uso di tipi valore come delegate.
Se il delegate viene impostato dopo aver chiamato un metodo asincrono, i primi eventi potrebbero essere persi. Esempio: chiamare startDownload() prima di assegnare manager.delegate = self comporta la perdita del callback di completamento se il download viene eseguito in modo sincrono o molto rapidamente. Soluzione: impostare il delegate prima di chiamare il metodo asincrono e documentare l'ordine di inizializzazione nei commenti del protocollo.
Domande frequenti
Weak impedisce un retain cycle tra il delegate e l'oggetto delegante. Se il riferimento fosse forte, gli oggetti si terrebbero reciprocamente e ARC non potrebbe liberarli. Un riferimento weak diventa automaticamente nil quando il delegate viene deallocato. Questa è una pratica standard di Cocoa Touch dall'avvento di Objective-C ed è preservata in Swift per la retrocompatibilità.
Delegate gestisce gli eventi e il comportamento (altezza delle celle, risposta ai tocchi). DataSource fornisce i dati da visualizzare (numero di righe, celle). Il delegato risponde alla domanda “come?”, dataSource risponde alla domanda “cosa?”. In iOS, entrambi sono implementati tramite protocolli, spesso nello stesso controller, ma sono concettualmente separati.
No, se il protocollo eredita da AnyObject (protocollo di classe). I riferimenti weak sono disponibili solo per i tipi riferimento (classi). Per i tipi valore (struct, enum), utilizzare callback closures o una classe wrapper separata. Se si controlla il protocollo, si può evitare di ereditare AnyObject, ma allora weak è vietato — scegliere consapevolmente tra delegate weak e delegate struct.
responds(to:) è un metodo di NSObjectProtocol che verifica se un oggetto implementa il selettore specificato. Viene utilizzato per verificare i metodi opzionali @objc del protocollo prima di chiamarli. Senza questa verifica, chiamare un metodo opzionale non implementato causerebbe NSInvalidArgumentException. In Swift, per i protocolli con @objc optional, la verifica può essere implicita tramite optional binding.
No, delegate è un pattern di delega, non un singleton. A differenza di un singleton, un delegate può essere sostituito in fase di esecuzione ed esiste in una singola istanza per ogni oggetto delegante. Un oggetto può essere delegate per più mittenti. Singleton è un pattern creazionale che garantisce una singola istanza di classe, che non ha nulla a che fare con la delega.
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