Delegate — είναι ένα μοτίβο σχεδίασης στο οποίο ένα αντικείμενο αναθέτει την εκτέλεση εργασιών σε άλλο αντικείμενο μέσω ενός πρωτοκόλλου με προκαθορισμένες μεθόδους. Στην ανάπτυξη iOS, το Delegate είναι ένα από τα θεμελιώδη μοτίβα του Cocoa Touch, που χρησιμοποιείται για ασύγχρονη ειδοποίηση χωρίς άμεση σύνδεση μεταξύ αποστολέα και παραλήπτη. Σύμφωνα με το Apple Documentation (2025), η αντιπροσώπευση εφαρμόζεται στα Foundation και UIKit για την επεξεργασία γεγονότων πινάκων, αιτημάτων δικτύου και διαχείρισης τοποθεσίας. Το μοτίβο εξασφαλίζει χαλαρή σύζευξη στοιχείων και επαναχρησιμοποίηση κώδικα.
Βασικά σημεία
Delegate (αντιπρόσωπος) — είναι ένα αντικείμενο που υλοποιεί ένα συγκεκριμένο πρωτόκολλο και λαμβάνει ειδοποιήσεις για γεγονότα ενός άλλου αντικειμένου. Το μοτίβο Delegation είναι μια εναλλακτική της κληρονομικότητας: αντί να δημιουργεί μια υποκλάση για την παράκαμψη μεθόδων, το αντικείμενο αναθέτει την επεξεργασία γεγονότων σε ένα εξωτερικό αντικείμενο. Στο iOS, η delegation υλοποιείται μέσω πρωτοκόλλων Swift με υποχρεωτικές και προαιρετικές μεθόδους. Η ιδιότητα delegate δηλώνεται πάντα ως weak var για να αποφευχθούν κυκλικές αναφορές μεταξύ αντικειμένων.
Το πρωτόκολλο delegate ορίζει τη σύμβαση αλληλεπίδρασης μεταξύ αντικειμένων. Υποχρεωτικές μέθοδοι πρέπει να υλοποιηθούν από τον αντιπρόσωπο, διαφορετικά ο κώδικας δεν θα μεταγλωττιστεί. Προαιρετικές μέθοδοι επισημαίνονται με το χαρακτηριστικό @objc optional και επιτρέπουν στον αντιπρόσωπο να αντιδρά μόνο στα απαραίτητα γεγονότα. Τα ονόματα μεθόδων ακολουθούν τη σύμβαση: πρώτη παράμετρος — το αντικείμενο-αποστολέας, δεύτερη — τα δεδομένα γεγονότος. Για παράδειγμα, το tableView(_:didSelectRowAt:) υποδεικνύει ότι ο αποστολέας είναι UITableView και τα δεδομένα είναι το ευρετήριο της επιλεγμένης γραμμής.
// Πρωτόκολλο 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)
}
// Κλάση που χρησιμοποιεί 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()
}
}
Η ιδιότητα delegate πρέπει να δηλωθεί ως weak var (ασθενής δείκτης) για την αποφυγή retain cycle. Εάν η αναφορά ήταν ισχυρή, ο αντιπρόσωπος και το αντικείμενο αντιπροσώπευσης θα κρατούσαν το ένα το άλλο και η ARC δεν θα μπορούσε να ελευθερώσει τη μνήμη τους. Τα πρωτόκολλα delegate κληρονομούν το AnyObject (μόνο για κλάσεις), το οποίο επιτρέπει τη χρήση weak. Οι δομές (struct) και τα enum δεν μπορούν να είναι αντιπρόσωποι λόγω της σημασιολογίας τιμών. Εναλλακτική για value types — callback closures.
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — κανένας retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
Το μοτίβο Delegate λειτουργεί με την αρχή “ένας-προς-έναν”: ένα αντικείμενο-αποστολέας μπορεί να έχει μόνο έναν αντιπρόσωπο σε μια συγκεκριμένη χρονική στιγμή. Όταν συμβαίνει ένα γεγονός, ο αποστολέας ελέγχει αν έχει οριστεί ο delegate και καλεί την αντίστοιχη μέθοδο του πρωτοκόλλου. Πλεονέκτημα έναντι της άμεσης κλήσης — ο αποστολέας δεν γνωρίζει τον τύπο του αντιπροσώπου, μόνο ότι αντιστοιχεί στο πρωτόκολλο. Αυτό συμμορφώνεται με την αρχή αντιστροφής εξάρτησης (DIP) από το SOLID.
Ο αντιπρόσωπος ορίζεται μέσω ανάθεσης: someObject.delegate = self. Κατά την αποδέσμευση του αντιπροσώπου, η ιδιότητα γίνεται αυτόματα nil λόγω της σημασιολογίας weak. Πριν από την κλήση της μεθόδου delegate, γίνεται έλεγχος μέσω optional chaining: delegate?.method(). Αν ο delegate είναι nil — η κλήση αγνοείται χωρίς crash. Για τις προαιρετικές μεθόδους πρωτοκόλλου χρησιμοποιείται επιπλέον έλεγχος: delegate?.responds(to: #selector(...)), αν και στη Swift αυτός ο έλεγχος είναι συνήθως σιωπηρός μέσω της δήλωσης προαιρετικής μεθόδου.
Σε περιβάλλον πολλαπλών νημάτων, ο delegate χρησιμοποιείται για ασύγχρονη επιστροφή αποτελέσματος. URLSession παρέχει URLSessionDelegate με μεθόδους που καλούνται κατά τη λήψη δεδομένων, timeout ή σφάλμα αυθεντικοποίησης. Οι μέθοδοι delegate εκτελούνται στην ουρά παρασκηνίου του URLSession, επομένως απαιτείται δρομολόγηση στο main queue για ενημερώσεις UI. Ο ασύγχρονος delegate δεν μπλοκάρει το νήμα κλήσης, επιτρέποντας τη συνέχιση άλλων εργασιών.
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 και Callback λύνουν την ίδια εργασία — ασύγχρονη ειδοποίηση — αλλά με διαφορετικούς τρόπους. Ο Delegate χρησιμοποιεί πρωτόκολλο με ονομασμένες μεθόδους, το callback — closure με καταγραφή περιβάλλοντος. Η επιλογή εξαρτάται από τον αριθμό γεγονότων, την πολυπλοκότητα υπογραφών και αρχιτεκτονικές προτιμήσεις. Η Apple συνιστά τον delegate για API με πολλαπλά γεγονότα (UITableView — 20+ μέθοδοι) και το callback για εφάπαξ ολοκληρώσεις.
Ο Delegate είναι προτιμότερος όταν πρέπει να επεξεργαστούν πολλαπλά διαφορετικά γεγονότα από μία πηγή. Για παράδειγμα, το CLLocationManager ειδοποιεί τον αντιπρόσωπο για αλλαγή τοποθεσίας, σφάλματα αδειών, είσοδο/έξοδο από γεωζώνες και αλλαγή κατάστασης υπηρεσιών. Κάθε γεγονός είναι μια ξεχωριστή μέθοδος πρωτοκόλλου με σαφές όνομα και τυποποιημένες παραμέτρους. Ο Delegate είναι επίσης βολικός για διαμόρφωση συμπεριφοράς (μέθοδοι should, will, did).
Το Callback είναι απλούστερο για εφάπαξ αιτήματα με ένα αποτέλεσμα. Completion handler στο URLSession.dataTask καταλαμβάνει μία γραμμή στο σημείο κλήσης έναντι τουλάχιστον τριών μεθόδων πρωτοκόλλου. Το Callback είναι επίσης πιο φυσικό για λειτουργικές αλυσίδες (map, flatMap, async/await). Ωστόσο, σε ένθεση άνω των 2-3 επιπέδων, το callback μετατρέπεται σε Callback Hell, ενώ ο delegate παραμένει πάντα επίπεδος.
Το iOS SDK περιέχει δεκάδες ενσωματωμένα πρωτόκολλα delegate για διάφορα υποσυστήματα. Κάθε ένα έχει σχεδιαστεί για ένα συγκεκριμένο σενάριο αλληλεπίδρασης. Σύμφωνα με το Apple Documentation (2025), οι πιο συχνά χρησιμοποιούμενοι delegates είναι UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate και UNUserNotificationCenterDelegate. Αυτά τα πρωτόκολλα περιέχουν 3 έως 30 μεθόδους με διαφορετικό βαθμό υποχρεωτικότητας.
UITableViewDelegate διαχειρίζεται την εμφάνιση και συμπεριφορά κελιών πίνακα. Περιέχει μεθόδους για επεξεργασία επιλογής γραμμής, ρύθμιση ύψους κελιών, προσαρμοσμένες προβολές header/footer και ενέργειες ολίσθησης. Όλες οι μέθοδοι του πρωτοκόλλου είναι προαιρετικές, επιτρέποντας την υλοποίηση μόνο της απαραίτητης λειτουργικότητας. Απουσία αντιπροσώπου, ο πίνακας λειτουργεί με προεπιλεγμένες ρυθμίσεις. Ιστορικά, ο delegate συνδυαζόταν με το UITableViewDataSource.
URLSessionDelegate παρέχει λεπτομερή έλεγχο επί των αιτημάτων HTTP. Οι μέθοδοι του αντιπροσώπου καλούνται κατά τη λήψη απόκρισης διακομιστή, δεδομένων, ολοκλήρωσης λήψης. Τα εξειδικευμένα υποπρωτόκολλα URLSessionTaskDelegate και URLSessionDataDelegate επεκτείνουν τη βασική λειτουργικότητα για συγκεκριμένους τύπους εργασιών. Ο Delegate απαιτείται για υποστήριξη λήψεων παρασκηνίου, πιστοποιητικών SSL και προσαρμοσμένης επεξεργασίας ανακατευθύνσεων.
| Delegate | Μέθοδοι | Σκοπός |
|---|---|---|
| UITableViewDelegate | 25 | Εμφάνιση και αλληλεπίδραση με πίνακα |
| UITextFieldDelegate | 8 | Επεξεργασία εισαγωγής κειμένου και πληκτρολογίου |
| CLLocationManagerDelegate | 12 | Ενημέρωση τοποθεσίας και γεωζώνες |
| URLSessionDelegate | 6 | Διαχείριση περιόδου HTTP και πιστοποιητικών |
| UNUserNotificationCenterDelegate | 4 | Επεξεργασία push ειδοποιήσεων σε πρώτο πλάνο |
Η διαχείριση μνήμης είναι μια κρίσιμη πτυχή της εργασίας με τον delegate στο iOS. ARC (Automatic Reference Counting) διαχειρίζεται αυτόματα τη μνήμη, αλλά μόνο με σωστή χρήση weak/unowned αναφορών. Η παραβίαση κανόνων οδηγεί σε διαρροές μνήμης ή πρόωρη αποδέσμευση. Ο delegate δηλωμένος ως strong δημιουργεί retain cycle αν ο κάτοχος του αντιπροσώπου διατηρεί επίσης αναφορά στο αντικείμενο αντιπροσώπευσης.
Retain cycle προκύπτει όταν το αντικείμενο A (κάτοχος) ορίζει τον εαυτό του ως αντιπρόσωπο του αντικειμένου B, και το B διατηρεί ισχυρή αναφορά στον delegate. Παράδειγμα: το ViewController δημιουργεί μια URLSession, ορίζει το self ως αντιπρόσωπο της περιόδου, αλλά η URLSession από προεπιλογή διατηρεί ισχυρή αναφορά στον delegate αν δεν έχει καθοριστεί delegateQueue. Λύση — ελέγχετε πάντα την τεκμηρίωση API για τον τύπο αναφοράς στον delegate (weak ή strong) και μηδενίστε ρητά τον delegate στο deinit.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// Μηδενισμός delegate στο deinit — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// URLSession με weak delegate μέσω 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
)
}
}
Πριν από την κλήση της μεθόδου του αντιπροσώπου, πρέπει να ελεγχθεί ότι ο αντιπρόσωπος υπάρχει (δεν είναι nil) και υλοποιεί την καλούμενη μέθοδο. Για υποχρεωτικές μεθόδους πρωτοκόλλου, δεν απαιτείται έλεγχος — ο μεταγλωττιστής εγγυάται την υλοποίηση. Για προαιρετικές μεθόδους χρησιμοποιήστε respond(to:) ή optional chaining. Εάν ο αντιπρόσωπος έχει αποδεσμευτεί, η weak αναφορά γίνεται αυτόματα nil και η κλήση του αντιπροσώπου αγνοείται. Αυτή είναι ασφαλής συμπεριφορά που δεν απαιτεί πρόσθετη επεξεργασία.
Οι προγραμματιστές συχνά κάνουν λάθη κατά την εργασία με το μοτίβο Delegate, ιδιαίτερα στα αρχικά στάδια εκμάθησης του iOS. Πιο συνηθισμένα: retain cycle λόγω strong delegate, λήθη κλήσης delegate?.method(), λανθασμένη υπογραφή μεθόδων πρωτοκόλλου, ορισμός delegate μετά την έναρξη λειτουργίας και συγκρούσεις πολλαπλών νημάτων. Ας εξετάσουμε κάθε λάθος και τρόπους πρόληψής του.
Το πιο κρίσιμο λάθος — δήλωση της ιδιότητας delegate ως strong var αντί weak var. Αυτό δημιουργεί ένα retain cycle όπου ούτε ο αντιπρόσωπος ούτε το αντικείμενο αντιπροσώπευσης μπορούν να ελευθερωθούν. Συνέπειες: διαρροή μνήμης, επιβράδυνση εφαρμογής και κρυμμένα σφάλματα. Λύση: χρησιμοποιείτε πάντα weak var για τον delegate και κληρονομήστε το πρωτόκολλο από AnyObject για να αποκλειστεί η χρήση value types ως αντιπροσώπου.
Εάν ο delegate ορίζεται μετά την κλήση μιας ασύγχρονης μεθόδου, τα πρώτα γεγονότα μπορεί να χαθούν. Παράδειγμα: κλήση startDownload() πριν από την ανάθεση manager.delegate = self οδηγεί σε απώλεια callback ολοκλήρωσης, εάν η λήψη εκτελείται σύγχρονα ή πολύ γρήγορα. Λύση: ορίστε τον delegate πριν από την κλήση της ασύγχρονης μεθόδου και τεκμηριώστε τη σειρά αρχικοποίησης στα σχόλια του πρωτοκόλλου.
Συχνές ερωτήσεις
Weak αποτρέπει τον retain cycle μεταξύ του αντιπροσώπου και του αντικειμένου αντιπροσώπευσης. Εάν η αναφορά ήταν strong, τα αντικείμενα θα κρατούσαν το ένα το άλλο και η ARC δεν θα μπορούσε να τα ελευθερώσει. Η weak-αναφορά γίνεται αυτόματα nil κατά την αποδέσμευση του αντιπροσώπου. Αυτή είναι τυπική πρακτική του Cocoa Touch από την εμφάνιση του Objective-C και διατηρήθηκε στη Swift για συμβατότητα προς τα πίσω.
Delegate επεξεργάζεται γεγονότα και διαχειρίζεται συμπεριφορά (ύψος κελιών, αντίδραση σε κλικ). DataSource παρέχει δεδομένα για εμφάνιση (αριθμός γραμμών, κελιά). Ο Delegate απαντά στην ερώτηση “πώς;”, το dataSource — στην ερώτηση “τι;”. Στο iOS, και τα δύο υλοποιούνται μέσω πρωτοκόλλων, συχνά στον ίδιο ελεγκτή, αλλά εννοιολογικά διαχωρίζονται.
Όχι, εάν το πρωτόκολλο κληρονομεί το AnyObject (πρωτόκολλο κλάσης). Οι weak-αναφορές είναι διαθέσιμες μόνο για reference types (κλάσεις). Για value types (struct, enum) χρησιμοποιήστε callback closures ή ξεχωριστή κλάση περιτυλίγματος. Εάν ελέγχετε το πρωτόκολλο, μπορείτε να μην κληρονομήσετε AnyObject, αλλά τότε το weak απαγορεύεται — επιλέξτε συνειδητά μεταξύ weak-delegate και struct-αντιπροσώπου.
responds(to:) — μέθοδος του NSObjectProtocol που ελέγχει αν το αντικείμενο υλοποιεί τον καθορισμένο selector. Χρησιμοποιείται για έλεγχο προαιρετικών @objc μεθόδων πρωτοκόλλου πριν από την κλήση. Χωρίς αυτόν τον έλεγχο, η κλήση μιας μη υλοποιημένης προαιρετικής μεθόδου θα οδηγήσει σε NSInvalidArgumentException. Στη Swift για πρωτόκολλα με @objc optional, ο έλεγχος μπορεί να είναι σιωπηρός μέσω optional binding.
Όχι, ο delegate είναι ένα μοτίβο αντιπροσώπευσης, όχι singleton. Σε αντίθεση με το singleton, ο αντιπρόσωπος μπορεί να αντικατασταθεί κατά το runtime και υπάρχει σε ένα στιγμιότυπο για κάθε αντικείμενο αντιπροσώπευσης. Ένα αντικείμενο μπορεί να είναι αντιπρόσωπος για πολλούς αποστολείς. Το Singleton είναι ένα δημιουργικό μοτίβο που εγγυάται ένα μοναδικό στιγμιότυπο κλάσης και δεν έχει καμία σχέση με την αντιπροσώπευση.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης