Delegate — είναι ένα μοτίβο σχεδίασης κατά το οποίο ένα αντικείμενο αναθέτει την εκτέλεση μιας εργασίας σε άλλο αντικείμενο. Στο iOS, το μοτίβο υλοποιείται μέσω πρωτοκόλλων Swift και @protocol Objective-C. Η Delegation είναι ένα από τα θεμελιώδη μοτίβα του Cocoa Touch, που χρησιμοποιείται σε UITableViewDelegate, UITextFieldDelegate και εκατοντάδες άλλα API της Apple. Σύμφωνα με την 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 και της λογικής επικύρωσης.
Η επιλογή μεταξύ αντιπροσώπου, ειδοποιήσεων και closures εξαρτάται από τον αριθμό των παραληπτών και τον βαθμό σύζευξης των στοιχείων. Κάθε μηχανισμός λύνει το πρόβλημα επικοινωνίας μεταξύ αντικειμένων, αλλά με διαφορετικούς συμβιβασμούς.
| Χαρακτηριστικό | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| Τύπος σύνδεσης | 1:1 | 1:N | 1:1 |
| Σύζευξη | Ασθενής (μέσω πρωτοκόλλου) | Πολύ ασθενής (κλειδί-συμβολοσειρά) | Μέτρια (σύλληψη περιβάλλοντος) |
| Ασφάλεια τύπων | Πλήρης | Απουσιάζει (Any?) | Πλήρης |
| Κίνδυνος retain cycle | Όχι (weak) | Όχι | Ναι (σύλληψη self) |
| Πότε να εφαρμόζεται | Σύνθετα callback με πολλαπλές μεθόδους | Γεγονότα που ενδιαφέρουν πολλούς | Απλά closures με 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 και closures. Ωστόσο, το 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης