Weak Reference (αδύναμη αναφορά) — είναι μια αναφορά σε ένα αντικείμενο που δεν αυξάνει τον μετρητή συγκράτησής του στο ARC. Σύμφωνα με τον Apple Swift Language Guide, 2026, οι weak αναφορές δηλώνονται με τη λέξη-κλειδί weak και έχουν πάντα προαιρετικό τύπο. Όταν το αντικείμενο ελευθερώνεται, όλες οι weak αναφορές σε αυτό ορίζονται αυτόματα σε nil, αποτρέποντας τους αιωρούμενους δείκτες και καθιστώντας τις αδύναμες αναφορές έναν ασφαλή μηχανισμό για τη διάσπαση του retain cycle.
Κύρια
weak πριν από var; τύπος πάντα προαιρετικός (?)Weak Reference — είναι μια μη κατέχουσα αναφορά σε ένα αντικείμενο στο ARC (Automatic Reference Counting). Σε αντίθεση με την strong αναφορά, η οποία αυξάνει το retain count του αντικειμένου και εγγυάται τη ζωή του, η weak αναφορά επιτρέπει στο αντικείμενο να ελευθερωθεί ακόμα κι αν υπάρχουν ακόμα αναφορές σε αυτό. Μετά την απελευθέρωση, η weak αναφορά ορίζεται αυτόματα σε nil — αυτό ονομάζεται zeroing weak.
Zeroing weak — βασικό χαρακτηριστικό του runtime της Swift και Objective-C. Όταν ο μετρητής αναφορών του αντικειμένου φτάσει στο μηδέν και το αντικείμενο αποδεσμευτεί, το runtime διατρέχει όλες τις weak αναφορές σε αυτό το αντικείμενο (αποθηκευμένες σε έναν ειδικό weak πίνακα) και τις ορίζει σε nil. Αυτό εγγυάται ότι η πρόσβαση στην ελευθερωμένη μνήμη (use-after-free) είναι αδύνατη μέσω weak αναφορών — οποιαδήποτε ανάγνωση επιστρέφει nil.
Σύμφωνα με το Apple WWDC 2012 Session 406, οι zeroing weak αναφορές εξάλειψαν μια ολόκληρη κατηγορία σφαλμάτων crash που σχετίζονταν με αιωρούμενους δείκτες (dangling pointers), τα οποία ήταν διαδεδομένα στη χειροκίνητη διαχείριση μνήμης (MRR). Στο MRR οι αδύναμες αναφορές υπήρχαν μόνο ως __unsafe_unretained — δεν μηδενίζονταν και η πρόσβαση σε ένα ελευθερωμένο αντικείμενο οδηγούσε σε EXC_BAD_ACCESS.
Ας εξετάσουμε τη σύνταξη δήλωσης weak αναφορών και στις δύο γλώσσες του οικοσυστήματος Apple. Παρά το κοινό runtime, η σύνταξη διαφέρει, αλλά η σημασιολογία είναι ταυτόσημη.
Στη Swift οι weak αναφορές δηλώνονται με τη λέξη-κλειδί weak πριν από var. Ο τύπος πρέπει να είναι πάντα προαιρετικός (Type?), καθώς η αναφορά μπορεί να μηδενιστεί ανά πάσα στιγμή. Οι σταθερές (let) δεν μπορούν να είναι weak — μόνο μεταβλητές.
class ViewController: UIViewController {
// weak-ιδιότητες: μόνο var, μόνο optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ closures δεν αποθηκεύουν weak
// ⬆️ Σφάλμα: weak μπορεί να εφαρμοστεί μόνο σε class-types, όχι σε closure
}
Σημαντικό: το weak εφαρμόζεται μόνο σε στιγμιότυπα κλάσεων (class-types), AnyObject και πρωτόκολλα που κληρονομούν από AnyObject. Struct, enum και closure δεν μπορούν να είναι weak — είναι τύποι τιμής και δεν συμμετέχουν στο ARC.
Στο Objective-C οι weak ιδιότητες δηλώνονται μέσω του χαρακτηριστικού __weak ή του τροποποιητή weak στο property:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Τοπική weak-μεταβλητή
__weak MyObject *weakRef = someStrongObject;
Το Objective-C runtime παρέχει επίσης zeroing weak, αλλά επιπλέον αποκλείει τη χρήση weak με C-δομές και ορισμένα αντικείμενα Core Foundation. Για αυτά χρησιμοποιείται __unsafe_unretained — χωρίς zeroing.
Οι αδύναμες αναφορές — δεν είναι καθολική λύση, αλλά εργαλείο για συγκεκριμένα σενάρια. Η χρήση weak παντού οδηγεί σε υπερβολική πολυπλοκότητα και υποβαθμίζει την αναγνωσιμότητα. Ας εξετάσουμε τα σωστά σενάρια εφαρμογής.
Εκπρόσωπος (Delegate) — το κύριο σενάριο για weak. Το αντικείμενο-ιδιοκτήτης (π.χ. UITableView) κρατά strong αναφορά στον εαυτό του, και ο εκπρόσωπος (UIViewController) δεν πρέπει να κατέχει τον πίνακα. Το Apple SDK εγγυάται ότι όλοι οι delegate και dataSource είναι weak. Για τα δικά σας πρωτόκολλα χρησιμοποιείτε πάντα weak var delegate.
Όταν το θυγατρικό αντικείμενο πρέπει να αναφέρεται στο γονικό (π.χ. ChildViewController για πρόσβαση στον συντονιστή), χρησιμοποιήστε weak αναφορά. Ο γονέας κατέχει το παιδί (strong), το παιδί παρατηρεί τον γονέα (weak) — retain cycle αποκλείεται.
Capture list [weak self] — ο τυπικός τρόπος αποφυγής retain cycle σε closures που αποθηκεύονται ως ιδιότητες κλάσης. Εάν το self μπορεί να ελευθερωθεί πριν την ολοκλήρωση του closure — weak self είναι υποχρεωτικό.
| Σενάριο | Weak | Strong |
|---|---|---|
| Delegate | ✅ Πάντα weak | ❌ Retain cycle |
| Parent → Child | ❌ Δεν χρειάζεται (ο γονέας πρέπει να κατέχει) | ✅ Strong |
| Child → Parent | ✅ Weak | ❌ Retain cycle |
| Ασύγχρονο callback | ✅ [weak self] | ❌ Κίνδυνος retain cycle |
| Ισχυρή σύνδεση (owned) | ❌ unowned | ✅ Strong |
Γενικός κανόνας: εάν το αντικείμενο A κατέχει το B (A → B strong), τότε το B → A πρέπει να είναι weak ή unowned. Η κατεύθυνση των strong αναφορών πρέπει πάντα να είναι από τον ιδιοκτήτη προς τον υφιστάμενο.
Τόσο το weak όσο και το unowned δεν αυξάνουν το retain count, αλλά διαφέρουν στη συμπεριφορά μετά την αποδέσμευση του αντικειμένου. Η επιλογή μεταξύ τους είναι θέμα εγγυήσεων ζωής.
Weak: μηδενίζεται αυτόματα (nil), τύπος πάντα optional, απαιτεί unwrap πριν από τη χρήση. Ασφαλές — η πρόσβαση σε nil δεν προκαλεί crash.
Unowned: δεν μηδενίζεται, τύπος non-optional. Εάν το αντικείμενο έχει ελευθερωθεί, η unowned αναφορά γίνεται αιωρούμενος δείκτης — η πρόσβαση σε αυτήν προκαλεί runtime crash. Το unowned υποθέτει ότι το αντικείμενο ζει όχι λιγότερο από το μέρος που αναφέρεται σε αυτό.
Weak επιλέξτε εάν: το αντικείμενο μπορεί να αποδεσμευτεί ανά πάσα στιγμή (εκπρόσωπος μετά το κλείσιμο οθόνης), δεν ελέγχετε τη διάρκεια ζωής του αντικειμένου ή αμφιβάλλετε για τις εγγυήσεις. Weak — καθολική ασφαλής επιλογή.
Unowned επιλέξτε εάν: το αντικείμενο είναι εγγυημένο ότι δεν μπορεί να ελευθερωθεί νωρίτερα από το αναφερόμενο (π.χ. Customer → CreditCard, όπου η κάρτα δεν υπάρχει χωρίς πελάτη). Το unowned δίνει non-optional API χωρίς unwrap, το οποίο είναι πιο βολικό στον κώδικα.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Ισχυρή σύνδεση: Order κατέχει το Item
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item δεν ζει χωρίς Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Παράδειγμα με weak: εκπρόσωπος χωρίς εγγύηση ζωής
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — εκπρόσωπος μπορεί να φύγει
}
Στο παράδειγμα, το Item χρησιμοποιεί unowned, επειδή το στοιχείο παραγγελίας δεν μπορεί να υπάρξει χωρίς την ίδια την παραγγελία — η εγγύηση ζωής είναι σιδερένια. Το NetworkService χρησιμοποιεί weak, επειδή ο εκπρόσωπος (π.χ. ViewController) μπορεί να κλείσει και να ελευθερωθεί ανά πάσα στιγμή.
Οι αδύναμες αναφορές — ισχυρό εργαλείο, αλλά έχουν περιορισμούς που είναι σημαντικό να κατανοήσετε για σωστή εφαρμογή στην ανάπτυξη iOS.
Οι αδύναμες αναφορές είναι πιο αργές από τις strong: σε κάθε πρόσβαση το runtime ελέγχει εάν το αντικείμενο έχει ελευθερωθεί (lookup στον weak πίνακα). Στη συντριπτική πλειονότητα των σεναρίων η διαφορά είναι αόρατη, αλλά σε καυτούς βρόχους με εκατομμύρια προσβάσεις το weak μπορεί να είναι σημείο συμφόρησης. Για σενάρια υψηλού φόρτου χρησιμοποιήστε strong και αναδιοργανώστε την αρχιτεκτονική.
Struct, enum, tuple — τύποι τιμής που δεν συμμετέχουν στο ARC. Η προσπάθεια δήλωσης weak struct οδηγεί σε σφάλμα μεταγλώττισης. Για αποθήκευση αδύναμης αναφοράς σε τύπο τιμής χρησιμοποιήστε wrapper σε class-type ή closure.
Zeroing weak είναι thread-safe: εάν το αντικείμενο ελευθερώνεται σε ένα νήμα, η weak αναφορά μηδενίζεται σε όλα τα νήματα ατομικά. Ωστόσο, το διάστημα μεταξύ ανάγνωσης της weak αναφοράς και χρήσης της μπορεί να οδηγήσει σε συνθήκη ανταγωνισμού — το αντικείμενο ελευθερώνεται μεταξύ λήψης της weak αναφοράς και χρήσης της. Λύση: strong-σύλληψη της αδύναμης αναφοράς σε μια τοπική μεταβλητή.
// Συνθήκη ανταγωνισμού με weak σε πολυνηματικό περιβάλλον
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf μπορεί να είναι nil μεταξύ ελέγχου και χρήσης
if weakSelf != nil {
weakSelf!.doSomething() // CRASH αν έγινε nil
}
}
}
// ✅ Διόρθωση: ισχυρή σύλληψη για τη διάρκεια χρήσης
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — τοπική strong αναφορά
}
}
Στην ασφαλή παραλλαγή, το weak self συλλαμβάνεται, στη συνέχεια αμέσως αποσυσκευάζεται σε μια τοπική strong μεταβλητή strongSelf. Εάν το self είναι ακόμα ζωντανό — θα παραμείνει ζωντανό κατά την εκτέλεση του μπλοκ. Εάν όχι — το guard ενεργοποιείται και ο κώδικας δεν εκτελείται. Αυτό το idiom — το τυπικό μοτίβο για ασύγχρονα closures στη Swift.
IBOutlet στο Interface Builder πρέπει να είναι weak, επειδή η ιεραρχία προβολής ήδη κρατά strong αναφορά στο subview. Η αντιγραφή της strong αναφοράς στον ελεγκτή δεν δημιουργεί retain cycle, αλλά είναι περιττή. Η αδύναμη αναφορά σε outlet — σύσταση της Apple, αν και πολλοί προγραμματιστές χρησιμοποιούν strong για απλοποίηση του κώδικα.
Συχνές Ερωτήσεις
Όχι, το weak μπορεί να δείχνει μόνο σε υπάρχον αντικείμενο ή nil. Κατά τη δημιουργία ενός νέου αντικειμένου, πρώτα λαμβάνετε μια strong αναφορά (μέσω του αρχικοποιητή), και μόνο τότε μπορείτε να εκχωρήσετε μια αδύναμη αναφορά. weak nil στην αρχή — φυσιολογική κατάσταση.
Το Weak βασίζεται στο ARC, το οποίο διαχειρίζεται μόνο τύπους αναφοράς (κλάσεις). Οι τύποι τιμής (struct, enum) αντιγράφονται κατά την εκχώρηση και δεν έχουν retain count. Για αδύναμη σύνδεση τύπων τιμής χρησιμοποιήστε closures ή wrapper σε class με weak ιδιότητα.
Κάθε πρόσβαση σε weak αναφορά εκτελεί ένα lookup στον πίνακα runtime. Σε έναν βρόχο με εκατομμύρια επαναλήψεις αυτό μπορεί να είναι 2–5 φορές πιο αργό από μια strong αναφορά. Για καυτές διαδρομές αντιγράψτε το weak σε μια τοπική strong μεταβλητή πριν από τον βρόχο.
Όταν όλες οι strong αναφορές στο αντικείμενο χαθούν — στο τέλος του πεδίου εμβέλειας, κατά την επαναφορά ιδιότητας, κατά το κλείσιμο οθόνης. Σε πολυνηματικό περιβάλλον αυτό μπορεί να συμβεί μεταξύ δύο γραμμών κώδικα. Πάντα ελέγχετε το weak μέσω guard let ή if let.
Σημασιολογικά ταυτόσημο: και τα δύο παρέχουν zeroing weak. Διαφορές: Η Swift απαιτεί optional τύπο και var, το Objective-C χρησιμοποιεί τροποποιητή property. Το Objective-C υποστηρίζει επίσης __unsafe_unretained — αδύναμη αναφορά χωρίς zeroing (κίνδυνος αιωρούμενου δείκτη).
Περίληψη
weak var + προαιρετικός τύπος; μόνο class-types και πρωτόκολλα AnyObjectΘα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης