ViewController Lifecycle — είναι μια ακολουθία μεθόδων που το UIKit καλεί αυτόματα κατά τη διαχείριση οθονών στο iOS. Σύμφωνα με το Apple Documentation, κάθε UIViewController περνά από ένα προβλέψιμο σύνολο καταστάσεων: από τη δημιουργία του View μέχρι την εμφάνιση και την απόκρυψή του. Η κατανόηση της σειράς και του σκοπού αυτών των μεθόδων είναι απαραίτητη προϋπόθεση για τη σταθερή λειτουργία της εφαρμογής iOS.
Κύρια Σημεία
ViewController Lifecycle — είναι ένα σύνολο μεθόδων που το UIViewController λαμβάνει από το UIKit κατά τη διάρκεια της ύπαρξής του. Κάθε οθόνη σε μια εφαρμογή iOS περνά διαδοχικά από τα στάδια δημιουργίας, φόρτωσης του View, εμφάνισης στην οθόνη, εξαφάνισης και απελευθέρωσης μνήμης. Το UIKit καλεί αυτόματα τις αντίστοιχες μεθόδους σε κάθε στάδιο και ο προγραμματιστής τις παρακάμπτει, προσθέτοντας τη δική του λογική.
Η αρχιτεκτονική του UIViewController αποτελεί τη βάση του UIKit και παραμένει επίκαιρη ακόμα και στην εποχή του SwiftUI — πολλά έργα χρησιμοποιούν ακόμα την κλασική προσέγγιση ή υβριδική αρχιτεκτονική. Η κατανόηση του Lifecycle επιτρέπει την πρόβλεψη του πότε είναι διαθέσιμα τα subviews, πότε μπορεί να τροποποιηθεί με ασφάλεια το layout και ποιες λειτουργίες πρέπει να εκτελούνται κατά την εμφάνιση ή απόκρυψη της οθόνης.
Κάθε μέθοδος του κύκλου ζωής έχει συγκεκριμένο σκοπό: μερικές καλούνται μία φορά κατά τη διάρκεια ζωής του ελεγκτή, άλλες — σε κάθε εμφάνιση ή εξαφάνιση. Η ανάμειξη λογικής μεταξύ μεθόδων οδηγεί σε δυσδιάκριτα σφάλματα: διαρροές μνήμης, εσφαλμένες ενημερώσεις δεδομένων και περιττά αιτήματα δικτύου.
Έξι μέθοδοι αποτελούν τον πλήρη κύκλο ζωής του UIViewController. Η σειρά κλήσης τους είναι σταθερή και δεν εξαρτάται από τον τρόπο πλοήγησης — push, present ή unwind segue ακολουθούν το ίδιο πρόγραμμα.
loadView — η πρώτη μέθοδος του κύκλου, καλείται όταν το View του ελεγκτή δεν υπάρχει ακόμα. Αν χρησιμοποιείτε Storyboard, το UIKit φορτώνει αυτόματα το View από το αρχείο xib. Κατά τον προγραμματισμό της διεπαφής, παρακάμπτετε αυτή τη μέθοδο, ορίζοντας χειροκίνητα το ριζικό View. Στα περισσότερα έργα, το loadView δεν πειράζεται — η εργασία γίνεται στο viewDidLoad.
Η παράκαμψη του loadView απαιτείται μόνο σε συγκεκριμένες περιπτώσεις: όταν ολόκληρη η διεπαφή δημιουργείται με κώδικα χωρίς Storyboard ή όταν το ριζικό View πρέπει να είναι από μη τυπική κλάση. Η Apple συνιστά να μην καλείτε το super.loadView κατά την παράκαμψη — αναλαμβάνετε πλήρως τη δημιουργία του View.
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
viewDidLoad — η πιο συχνά χρησιμοποιούμενη μέθοδος του κύκλου. Καλείται μία φορά αφού το View φορτωθεί στη μνήμη, αλλά πριν εμφανιστεί στην οθόνη. Εδώ ρυθμίζονται τα subviews, γεμίζονται οι πίνακες με δεδομένα, καταγράφονται τα κελιά και γίνεται εγγραφή σε ειδοποιήσεις που ισχύουν καθ’ όλη τη διάρκεια ζωής του ελεγκτή.
Σημαντικό χαρακτηριστικό: το viewDidLoad δεν καλείται ξανά κατά την επανεμφάνιση της οθόνης. Αν χρειάζεται να ενημερώνετε δεδομένα κάθε φορά που εμφανίζεται — χρησιμοποιήστε το viewWillAppear. Στο viewDidLoad τοποθετείτε μόνο εφάπαξ λειτουργίες από τις οποίες εξαρτάται η βασική διαμόρφωση.
viewWillAppear καλείται κάθε φορά αμέσως πριν το View γίνει ορατό στον χρήστη. Αυτή η μέθοδος λαμβάνει την παράμετρο animated, που υποδεικνύει αν η εμφάνιση γίνεται με κινούμενα σχέδια. Εδώ ενημερώνονται τα δεδομένα, φορτώνονται ξανά οι πίνακες, ρυθμίζεται το NavigationBar και κρύβονται ή εμφανίζονται στοιχεία ανάλογα με την κατάσταση της εφαρμογής.
Χρησιμοποιήστε το viewWillAppear για συγχρονισμό κατάστασης μεταξύ οθονών: αν ο χρήστης μπορεί να έχει αλλάξει δεδομένα στην προηγούμενη οθόνη, αυτή η μέθοδος είναι το κατάλληλο μέρος για ενημέρωση της διεπαφής. Κάθε κλήση viewWillAppear προηγείται της εμφάνισης της οθόνης, ακόμα και κατά την επιστροφή από έναν θυγατρικό ελεγκτή.
viewDidAppear ειδοποιεί ότι το View εμφανίστηκε πλήρως στην οθόνη και όλα τα κινούμενα σχέδια μετάβασης ολοκληρώθηκαν. Σε αυτή τη στιγμή, η οθόνη είναι έτοιμη για αλληλεπίδραση — ο χρήστης βλέπει την πλήρη διεπαφή και μπορεί να εργαστεί με αυτήν. Αυτή η μέθοδος είναι κατάλληλη για εκκίνηση κινούμενων σχεδίων που πρέπει να ξεκινήσουν μετά την εμφάνιση, εκκίνηση χρονομέτρων και παρακολούθηση αναλυτικών στοιχείων.
Σε αντίθεση με το viewWillAppear, το viewDidAppear εγγυάται ότι η οθόνη όχι μόνο είναι ορατή αλλά έχει πλήρως αποδοθεί. Αν ξεκινήσετε ένα κινούμενο σχέδιο στο viewWillAppear, ορισμένα καρέ μπορεί να παραλειφθούν επειδή το UIKit δεν έχει ολοκληρώσει ακόμα τη μετάβαση. Για ομαλά κινούμενα σχέδια, χρησιμοποιήστε το viewDidAppear.
viewWillDisappear καλείται πριν από την εξαφάνιση του View από την οθόνη — κατά τη μετάβαση σε άλλο ελεγκτή, κλείσιμο παραθύρου modal ή ελαχιστοποίηση της εφαρμογής. Αυτό είναι το κατάλληλο μέρος για αποθήκευση κατάστασης, διαγραφή εγγραφών από ειδοποιήσεις, διακοπή ενεργών διαδικασιών και απελευθέρωση πόρων που δεν χρειάζονται όταν η οθόνη δεν είναι ορατή.
Σημαντικό να θυμάστε: το viewWillDisappear δεν εγγυάται ότι το View θα εξαφανιστεί τελικά — η χειρονομία μπορεί να ακυρωθεί. Επομένως, αποθηκεύστε κρίσιμα δεδομένα και στο viewDidDisappear, το οποίο καλείται μόνο μετά την πραγματική εξαφάνιση.
viewDidDisappear ολοκληρώνει τον κύκλο εμφάνισης και εξαφάνισης. Καλείται αφού το View έχει ήδη κρυφτεί από την οθόνη. Σε αυτή τη μέθοδο, τα κινούμενα σχέδια σταματούν οριστικά, τα προσωρινά αντικείμενα διαγράφονται και η αποθήκευση δεδομένων που ξεκίνησε στο viewWillDisappear επιβεβαιώνεται.
Αυτή η μέθοδος προηγείται επίσης του deinit του ελεγκτή — αν το UIViewController σας καταστρέφεται, το viewDidDisappear θα είναι η τελευταία μέθοδος Lifecycle πριν από την κλήση του deinit. Χρησιμοποιήστε το για τον τελικό καθαρισμό που πρέπει να συμβεί πριν από την καταστροφή του αντικειμένου.
Η σειρά κλήσης εξαρτάται από το πώς ακριβώς εμφανίζεται η οθόνη: για πρώτη φορά, κατά την επιστροφή ή κατά την modal εμφάνιση. Ας εξετάσουμε τρία κύρια σενάρια από την προοπτική του UIKit.
Κατά την πρώτη εμφάνιση της οθόνης, το UIKit περνά από τον πλήρη κύκλο δημιουργίας: καλείται το loadView, στη συνέχεια το viewDidLoad, μετά το οποίο ξεκινά το κινούμενο σχέδιο εμφάνισης. Κατά τη διάρκεια του κινούμενου σχεδίου καλείται το viewWillAppear, και μετά την ολοκλήρωση — το viewDidAppear. Αυτό είναι το μοναδικό σενάριο στο οποίο όλες οι μέθοδοι από το loadView έως το viewDidAppear καλούνται διαδοχικά.
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad — View φορτώθηκε στη μνήμη")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear — θα εμφανιστεί σύντομα")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear — οθόνη πλήρως ορατή")
}
Όταν ο χρήστης επιστρέφει στην προηγούμενη οθόνη, το UIKit δεν καλεί ξανά το viewDidLoad — το View έχει ήδη φορτωθεί στη μνήμη. Αντί αυτού, στην επιστρέφουσα οθόνη ενεργοποιούνται μόνο τα viewWillAppear και viewDidAppear, και στην τρέχουσα — viewWillDisappear και viewDidDisappear. loadView και viewDidLoad παραλείπονται επειδή η οθόνη υπάρχει ήδη στη στοίβα πλοήγησης.
Modal εμφάνιση ακολουθεί τους ίδιους κανόνες: στον νέο ελεγκτή καλείται ο πλήρης κύκλος κατά την πρώτη εμφάνιση, και στον τρέχοντα — viewWillDisappear και viewDidDisappear. Κατά το dismiss, η σειρά αντιστρέφεται: στον επιστρέφοντα ελεγκτή ενεργοποιούνται ξανά τα viewWillAppear και viewDidAppear, και στον κρυμμένο — οι τελικές μέθοδοι. Αυτή η συμπεριφορά είναι ομοιόμορφη για όλους τους τύπους μεταβάσεων στο UIKit.
Ας εξετάσουμε τέσσερα βασικά σενάρια στα οποία η κατανόηση του Lifecycle επηρεάζει άμεσα την ποιότητα του κώδικα και την εμπειρία χρήστη. Για κάθε σενάριο δίνουμε ένα παράδειγμα με συστάσεις.
viewDidLoad — το μέρος για την αρχική ρύθμιση που δεν εξαρτάται από την ορατότητα της οθόνης. Εδώ ρυθμίζεται το collectionView, καταγράφονται τα αρχεία nib για τα κελιά, δημιουργείται το data source και το layout. Αν φορτώνετε δεδομένα από το δίκτυο, στο viewDidLoad είναι καλύτερα μόνο να ξεκινήσετε το αίτημα και να ενημερώσετε τη διεπαφή στο viewWillAppear, όταν η οθόνη είναι έτοιμη για εμφάνιση.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(
MyCell.self,
forCellReuseIdentifier: MyCell.identifier
)
viewModel.loadInitialData()
}
Χρησιμοποιήστε το viewWillAppear για συγχρονισμό δεδομένων κάθε φορά που εμφανίζεται η οθόνη. Για παράδειγμα, αν ο χρήστης μπορεί να έχει αλλάξει ρυθμίσεις στην προηγούμενη οθόνη, εδώ ενημερώνονται οι εμφανιζόμενες τιμές, φορτώνεται ξανά ο πίνακας και διορθώνεται η κατάσταση του NavigationBar. Αυτό εγγυάται ότι η οθόνη εμφανίζει πάντα επικαιροποιημένα δεδομένα σε οποιοδήποτε σενάριο πλοήγησης.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
navigationController?.setNavigationBarHidden(false, animated: animated)
}
viewDidAppear είναι ιδανικό για εκκίνηση κινούμενων σχεδίων που πρέπει να ξεκινήσουν αφού ο χρήστης δει την οθόνη. Εδώ επίσης αποστέλλονται γεγονότα αναλυτικών στοιχείων: εμφάνιση οθόνης, έναρξη onboarding ή έναρξη αναπαραγωγής βίντεο. Η εκκίνηση κινούμενων σχεδίων πριν την ολοκλήρωση της μετάβασης οδηγεί σε τραυλιάζουσα διεπαφή — το UIKit δεν προλαβαίνει να προετοιμάσει αρκετά καρέ.
Στο viewWillDisappear αποθηκεύονται πρόχειρα, σταματούν τα χρονόμετρα και γίνεται διαγραφή εγγραφών από το NotificationCenter. Αυτή είναι η τελευταία στιγμή που η οθόνη είναι ακόμα ορατή και διαθέσιμη για λειτουργίες που απαιτούν το περιβάλλον του χρήστη. Για κρίσιμα δεδομένα, επιπλέον χρησιμοποιείται το viewDidDisappear ως ασφάλεια έναντι ακυρωμένων χειρονομιών.
Η εσφαλμένη χρήση των μεθόδων του κύκλου ζωής είναι μια από τις πιο κοινές πηγές σφαλμάτων σε εφαρμογές iOS. Ας εξετάσουμε τα κύρια λάθη που κάνουν οι προγραμματιστές σε διάφορα στάδια εργασίας με το UIViewController.
Πρώτο λάθος — δημιουργία subviews στο init ή loadView όταν χρησιμοποιείται Storyboard. Αν χρησιμοποιείτε το Interface Builder, μην παρακάμπτετε το loadView χωρίς λόγο. Η δημιουργία View στο loadView με υπάρχον storyboard οδηγεί στην παράβλεψη του αρχείου xib και σε κενή οθόνη.
Δεύτερο λάθος — εγγραφή σε ειδοποιήσεις πληκτρολογίου στο viewDidLoad χωρίς διαγραφή. Αν έχετε εγγραφεί στο UIResponder.keyboardWillShowNotification αλλά δεν διαγράψατε την εγγραφή κατά την απόκρυψη της οθόνης, το μπλοκ θα καλείται ακόμα και μετά το deinit του ελεγκτή — αυτό είναι διαρροή μνήμης με πιθανή κατάρρευση της εφαρμογής.
Τρίτο λάθος — χρονόμετρα και αιτήματα δικτύου που ξεκινούν πριν από την εμφάνιση της οθόνης. Φόρτωση εικόνων ή εκτέλεση κινούμενων σχεδίων όταν το View δεν είναι ακόμα ορατό — σπατάλη πόρων. Μεταφέρετε τις οπτικές ενημερώσεις στο viewWillAppear ή viewDidAppear.
Τέταρτο λάθος — αποθήκευση δεδομένων μόνο στο viewWillDisappear. Κατά τη διαδραστική χειρονομία pop, ο χρήστης μπορεί να ξεκινήσει ένα σύρσιμο και να το ακυρώσει — η μέθοδος κλήθηκε, αλλά η οθόνη δεν εξαφανίστηκε. Αντιγράψτε την κρίσιμη αποθήκευση στο viewDidDisappear ή στο χειριστή applicationDidEnterBackground.
Συχνές Ερωτήσεις
Μία φορά — μετά τη φόρτωση του View στη μνήμη. Κατά τις επανεμφανίσεις της οθόνης, το viewDidLoad δεν καλείται. Αν χρειαστεί να αναδημιουργηθεί το View, ο ελεγκτής πρέπει να καταστραφεί και να δημιουργηθεί ξανά.
Το UIKit απαιτεί την κλήση του super.viewDidLoad για τη σωστή λειτουργία του κύκλου ζωής. Χωρίς αυτό, ενδέχεται να προκύψουν προβλήματα με την ενημέρωση του layout και την επεξεργασία μεταβάσεων. Πάντα καλείτε το super ως πρώτη εντολή στη μέθοδο.
Δεν συνιστάται. Αν ο ελεγκτής αρχικοποιηθεί από Storyboard, το UIKit φορτώνει αυτόματα το View από το xib. Η παράκαμψη του loadView ακυρώνει αυτή τη διαδικασία και το storyboard σας θα αγνοηθεί.
Εγγραφείτε στο viewDidLoad ή viewWillAppear και διαγράψτε την εγγραφή στο viewWillDisappear ή viewDidDisappear, χρησιμοποιώντας αδύναμη αναφορά στο self για αποφυγή διαρροών μνήμης στα closures.
Το Force quit σκοτώνει βίαια τη διαδικασία — το UIKit δεν προλαβαίνει να καλέσει τις μεθόδους Lifecycle. Για αποθήκευση δεδομένων, χρησιμοποιήστε την ειδοποίηση UIApplication.willTerminateNotification στο AppDelegate.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης