Delegate est un motif de conception où un objet délègue l'exécution d'une tâche à un autre objet. Sous iOS, le motif est implémenté via les protocoles Swift et @protocol Objective-C. La délégation est l'un des motifs fondamentaux de Cocoa Touch, utilisé dans UITableViewDelegate, UITextFieldDelegate et des centaines d'autres API Apple. Selon la documentation développeur Apple (2025), environ 70 % des classes système UIKit utilisent des délégués pour personnaliser le comportement sans héritage.
Points clés
Delegate est un motif de conception comportemental qui permet à un objet de déléguer une partie de ses responsabilités à un autre objet. Contrairement à l'héritage, où une classe enfant redéfinit les méthodes parentes, le délégué utilise la composition : l'objet propriétaire conserve une référence au délégué et appelle ses méthodes à des points spécifiques.
Le délégué définit un protocole — un ensemble de méthodes que le délégué peut implémenter. Les méthodes sont divisées en obligatoires (required) et optionnelles (optional). En Swift, les méthodes optionnelles du protocole sont marquées avec le mot-clé @objc optional.
L'objet A (propriétaire) contient une propriété delegate — une référence faible à l'objet B (délégué). Lorsqu'un événement se produit, A vérifie si B implémente la méthode de protocole correspondante et l'appelle. La référence faible est obligatoire : sans elle, le délégué ne peut pas être libéré de la mémoire car le propriétaire le retient avec une référence 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)
// chargement asynchrone
}
}La classe DataLoader définit le protocole LoaderDelegate et appelle les méthodes du délégué aux points clés du cycle de chargement. AnyObject garantit que le protocole ne peut être implémenté que par des classes — ceci est nécessaire pour la référence faible.
L'implémentation du délégué en Swift comprend trois étapes : déclarer le protocole, créer une propriété delegate faible dans le propriétaire et implémenter le protocole dans la classe déléguée. Prenons l'exemple d'un UITextField personnalisé avec validation.
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("Échec de la validation : saisie trop courte")
}
}LoginViewController implémente le protocole ValidatorDelegate et se désigne comme délégué de textField. À chaque modification de texte, ValidatedTextField appelle validate(_:), et si la validation échoue — validatorDidFail(_:). Le contrôleur agit comme intermédiaire entre la Vue et la logique de validation.
Le choix entre délégué, notifications et closures dépend du nombre de destinataires et du couplage des composants. Chaque mécanisme résout le problème de communication entre objets, mais avec des compromis différents.
| Caractéristique | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| Type de communication | 1:1 | 1:N | 1:1 |
| Couplage | Faible (via protocole) | Très faible (clé chaîne) | Moyen (capture de contexte) |
| Sécurité des types | Complète | Aucune (Any?) | Complète |
| Risque de cycle de rétention | Non (weak) | Non | Oui (capture de self) |
| Quand utiliser | Callbacks complexes avec plusieurs méthodes | Événements intéressant plusieurs | Closures simples avec 1-2 callbacks |
Delegate est optimal lorsque vous devez transmettre une série d'événements liés à un seul destinataire. NotificationCenter est mieux pour les notifications broadcast. Closure est pour les opérations asynchrones simples, comme les gestionnaires d'achèvement dans URLSession.
Objective-C utilise @protocol et @optional pour déclarer les délégués. Contrairement à Swift, toutes les méthodes du protocole sont optionnelles par défaut. La principale différence est l'appel respondsToSelector: avant d'envoyer un message au délégué, car la méthode peut ne pas être implémentée.
@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];
}
// chargement asynchrone d'image
}
@endLa principale différence en Objective-C : avant d'appeler une méthode optionnelle, une vérification respondsToSelector: est nécessaire. En Swift, les méthodes optionnelles du protocole éliminent cette vérification — l'optional chaining (?.) gère automatiquement l'absence d'implémentation.
Les erreurs d'utilisation des délégués entraînent des fuites mémoire, des crashs de l'application et des bugs non évidents. Examinons les cinq problèmes les plus courants.
Le cycle de rétention est l'erreur la plus courante. Si la propriété delegate est déclarée comme strong, et que le délégué possède à son tour l'objet propriétaire, un cycle de rétention se forme. Les deux objets ne seront jamais libérés de la mémoire. Solution : déclarez toujours le délégué comme weak var en Swift ou @property (weak) en Objective-C.
Si l'objet propriétaire survit au délégué et que la référence reste, l'appel de la méthode du délégué entraînera EXC_BAD_ACCESS. La référence faible résout ce problème automatiquement : après la libération du délégué, la propriété devient nil. Cependant, dans les scénarios multithread, vous devez vérifier supplémentairement le délégué sur le thread principal.
Un protocole avec plus de 20 méthodes viole le principe de ségrégation des interfaces (ISP). UITableViewDelegate contient environ 30 méthodes optionnelles — c'est une exception historique. Dans vos propres protocoles, il est préférable de diviser la responsabilité en plusieurs petits protocoles, chacun avec son propre rôle.
Les API système Apple utilisent activement le motif Delegate. Examinons trois exemples clés d'UIKit qui apparaissent dans chaque application iOS.
| API | Protocole | Méthodes clés |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
Chacun de ces protocoles implémente différents aspects du comportement : UITableViewDelegate gère l'apparence et la réponse tactile, UITextFieldDelegate contrôle la saisie de texte, URLSessionDelegate traite les événements réseau. Cela démontre la flexibilité du motif : le délégué peut être adapté à tout domaine de responsabilité.
Foire aux questions
Delegate gère le comportement et l'apparence (hauteur de cellule, réponse tactile). DataSource fournit les données (nombre de lignes, contenu des cellules). Dans UITableViewDelegate et UITableViewDataSource — ce sont deux protocoles séparés qui divisent la responsabilité entre présentation et données.
La référence faible empêche les cycles de rétention. Le propriétaire (par exemple, UITableView) ne conserve une référence au délégué qu'en weak. Si le délégué (UIViewController) possède la table, une référence forte au délégué créerait un cycle : ViewController → UITableView → Delegate (ViewController). Weak brise ce cycle.
Dans SwiftUI, le motif Delegate est moins utilisé — il est remplacé par @Binding, @State et les closures. Cependant, le délégué est toujours utilisé pour l'intégration UIKit via UIViewRepresentable. Par exemple, MKMapViewDelegate et WKUIDelegate restent pertinents lors de l'encapsulation de composants UIKit dans SwiftUI.
@objc optional permet de déclarer des méthodes optionnelles dans un protocole Swift. C'est un mécanisme de compatibilité avec l'environnement d'exécution Objective-C. Sans @objc, toutes les méthodes du protocole Swift sont obligatoires par défaut. Optional est utilisé dans les protocoles UIKit où le délégué ne peut implémenter que les méthodes dont il a besoin.
Un objet peut avoir un seul délégué pour chaque propriété delegate. Si vous devez notifier plusieurs objets, utilisez un délégué multicast, un tableau de délégués ou NotificationCenter. Le motif Delegate a été initialement conçu comme une relation 1:1.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi