ViewController Lifecycle — to sekwencja metod, które UIKit wywołuje automatycznie podczas zarządzania ekranami w iOS. Według Apple Documentation, każdy UIViewController przechodzi przez przewidywalny zestaw stanów: od utworzenia widoku do jego pojawienia się i ukrycia. Zrozumienie kolejności i przeznaczenia tych metod jest warunkiem koniecznym stabilnego działania aplikacji iOS.
Najważniejsze
ViewController Lifecycle — to zbiór metod, które UIViewController otrzymuje od UIKit w trakcie swojego istnienia. Każdy ekran w aplikacji iOS przechodzi kolejno przez etapy tworzenia, ładowania widoku, pojawiania się na ekranie, znikaniania i zwalniania pamięci. UIKit automatycznie wywołuje odpowiednie metody na każdym etapie, a programista je nadpisuje, dodając własną logikę.
Architektura UIViewController stanowi podstawę UIKit i pozostaje aktualna nawet w erze SwiftUI — wiele projektów wciąż używa klasycznego podejścia lub architektury hybrydowej. Zrozumienie Lifecycle pozwala przewidzieć, w którym momencie dostępne są subviews, kiedy można bezpiecznie modyfikować layout i jakie operacje wykonywać przy pojawianiu się lub ukrywaniu ekranu.
Każda metoda cyklu życia ma konkretne przeznaczenie: niektóre wywoływane są raz przez cały czas istnienia kontrolera, inne — przy każdym pojawieniu się lub zniknięciu. Mieszanie logiki pomiędzy metodami prowadzi do trudnych do wykrycia błędów: wycieków pamięci, nieprawidłowych aktualizacji danych i zbędnych zapytań sieciowych.
Sześć metod tworzy pełny cykl życia UIViewController. Kolejność ich wywołania jest stała i nie zależy od sposobu nawigacji — push, present czy unwind segue postępują według tego samego harmonogramu.
loadView — pierwsza metoda cyklu, wywoływana, gdy widok kontrolera jeszcze nie istnieje. Jeśli używasz Storyboard, UIKit automatycznie ładuje widok z pliku xib. Przy programowym tworzeniu interfejsu nadpisujesz tę metodę, ręcznie przypisując główny widok. W większości projektów loadView nie jest modyfikowany — praca odbywa się w viewDidLoad.
Nadpisanie loadView jest wymagane tylko w szczególnych przypadkach: gdy cały interfejs jest tworzony kodem bez Storyboard lub gdy główny widok musi być niestandardowej klasy. Apple zaleca, aby nie wywoływać super.loadView podczas nadpisywania — całkowicie przejmujesz tworzenie widoku na siebie.
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
viewDidLoad — najczęściej używana metoda cyklu. Jest wywoływana raz po załadowaniu widoku do pamięci, ale jeszcze przed jego wyświetleniem na ekranie. Tutaj konfiguruje się subviews, wypełnia tabele danymi, rejestruje komórki i subskrybuje powiadomienia działające przez cały czas życia kontrolera.
Ważna cecha: viewDidLoad nie jest wywoływane ponownie przy ponownym wyświetleniu ekranu. Jeśli chcesz aktualizować dane za każdym razem przy pojawieniu się — użyj viewWillAppear. W viewDidLoad umieszczaj tylko operacje jednorazowe, od których zależy podstawowa konfiguracja.
viewWillAppear jest wywoływany za każdym razem bezpośrednio przed tym, jak widok staje się widoczny dla użytkownika. Ta metoda otrzymuje parametr animated, wskazujący, czy pojawienie się odbywa się z animacją. Tutaj aktualizuje się dane, przeładowuje tabele, konfiguruje NavigationBar oraz ukrywa lub pokazuje elementy w zależności od stanu aplikacji.
Używaj viewWillAppear do synchronizacji stanu pomiędzy ekranami: jeśli użytkownik mógł zmienić dane na poprzednim ekranie, ta metoda jest właściwym miejscem do aktualizacji interfejsu. Każde wywołanie viewWillAppear poprzedza pojawienie się ekranu, nawet przy powrocie z kontrolera podrzędnego.
viewDidAppear informuje, że widok w pełni pojawił się na ekranie i wszystkie animacje przejścia zostały zakończone. W tym momencie ekran jest gotowy do interakcji — użytkownik widzi pełny interfejs i może z nim pracować. Ta metoda nadaje się do uruchamiania animacji, które mają rozpocząć się po pojawieniu, startu timerów i śledzenia wyświetleń analityki.
W przeciwieństwie do viewWillAppear, viewDidAppear gwarantuje, że ekran jest nie tylko widoczny, ale w pełni wyrenderowany. Jeśli uruchamiasz animację w viewWillAppear, część klatek może zostać pominięta, ponieważ UIKit nie zakończył jeszcze przejścia. Do płynnych animacji używaj viewDidAppear.
viewWillDisappear jest wywoływany przed zniknięciem widoku z ekranu — przy przejściu na inny kontroler, zamknięciu okna modalnego lub minimalizacji aplikacji. To właściwe miejsce do zapisywania stanu, wypisywania się z powiadomień, zatrzymywania aktywnych procesów i zwalniania zasobów, które nie są potrzebne, gdy ekran nie jest widoczny.
Ważne: viewWillDisappear nie gwarantuje, że widok ostatecznie zniknie — gest może zostać anulowany. Dlatego krytyczne dane zapisuj również w viewDidDisappear, który jest wywoływany dopiero po faktycznym zniknięciu.
viewDidDisappear kończy cykl pojawiania się i znikaniania. Jest wywoływany po tym, jak widok został już ukryty z ekranu. W tej metodzie ostatecznie zatrzymuje się animacje, usuwa obiekty tymczasowe i potwierdza zapis danych rozpoczęty w viewWillDisappear.
Ta metoda poprzedza również deinit kontrolera — jeśli twój UIViewController jest niszczony, viewDidDisappear będzie ostatnią metodą Lifecycle przed wywołaniem deinit. Użyj jej do końcowego czyszczenia, które powinno nastąpić przed zniszczeniem obiektu.
Kolejność wywołania zależy od tego, jak dokładnie pojawia się ekran: po raz pierwszy, przy powrocie lub przy wyświetleniu modalnym. Rozważmy trzy główne scenariusze z punktu widzenia UIKit.
Przy pierwszym pojawieniu się ekranu UIKit przechodzi pełny cykl tworzenia: wywoływane są loadView, następnie viewDidLoad, po czym rozpoczyna się animacja pojawiania. Podczas animacji wywoływany jest viewWillAppear, a po zakończeniu — viewDidAppear. To jedyny scenariusz, w którym wszystkie metody od loadView do viewDidAppear są wywoływane sekwencyjnie.
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad — widok załadowany do pamięci")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear — zaraz się pojawi")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear — ekran w pełni widoczny")
}
Gdy użytkownik wraca do poprzedniego ekranu, UIKit nie wywołuje ponownie viewDidLoad — widok jest już załadowany do pamięci. Zamiast tego na powracającym ekranie wywoływane są tylko viewWillAppear i viewDidAppear, a na bieżącym — viewWillDisappear i viewDidDisappear. loadView i viewDidLoad są pomijane, ponieważ ekran już istnieje w stosie nawigacji.
Wyświetlenie modalne podlega tym samym zasadom: u nowego kontrolera wywoływany jest pełny cykl przy pierwszym pojawieniu, a u bieżącego — viewWillDisappear i viewDidDisappear. Przy dismiss kolejność jest odwrotna: u powracającego kontrolera ponownie wywoływane są viewWillAppear i viewDidAppear, a u ukrywanego — metody kończące. To zachowanie jest jednolite dla wszystkich typów przejść w UIKit.
Rozważmy cztery kluczowe scenariusze, w których zrozumienie Lifecycle bezpośrednio wpływa na jakość kodu i doświadczenie użytkownika. Dla każdego scenariusza podajemy przykład z zaleceniami.
viewDidLoad — miejsce do podstawowej konfiguracji, która nie zależy od widoczności ekranu. Tutaj konfiguruje się collectionView, rejestruje pliki nib dla komórek, tworzy data source i layout. Jeśli ładujesz dane z sieci, w viewDidLoad lepiej tylko zainicjować żądanie, a aktualizować interfejs w viewWillAppear, gdy ekran jest gotowy do wyświetlenia.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(
MyCell.self,
forCellReuseIdentifier: MyCell.identifier
)
viewModel.loadInitialData()
}
Używaj viewWillAppear do synchronizacji danych za każdym razem przy pojawieniu się ekranu. Na przykład, jeśli użytkownik mógł zmienić ustawienia na poprzednim ekranie, tutaj aktualizuje się wyświetlane wartości, przeładowuje tabelę i koryguje stan NavigationBar. To gwarantuje, że ekran zawsze pokazuje aktualne dane przy każdym scenariuszu nawigacji.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
navigationController?.setNavigationBarHidden(false, animated: animated)
}
viewDidAppear doskonale nadaje się do uruchamiania animacji, które mają rozpocząć się po tym, jak użytkownik zobaczył ekran. Tutaj również wysyła się zdarzenia analityki: wyświetlenie ekranu, start onboardingu lub rozpoczęcie odtwarzania wideo. Uruchamianie animacji przed zakończeniem przejścia prowadzi do szarpanego interfejsu — UIKit nie zdąży przygotować wystarczającej liczby klatek.
W viewWillDisappear zapisuje się wersje robocze, zatrzymuje timery i wypisuje się z NotificationCenter. To ostatni moment, gdy ekran jest jeszcze widoczny i dostępny dla operacji wymagających kontekstu użytkownika. W przypadku krytycznych danych dodatkowo używa się viewDidDisappear jako zabezpieczenia przed anulowanymi gestami.
Nieprawidłowe użycie metod cyklu życia to jedno z najczęstszych źródeł błędów w aplikacjach iOS. Rozważmy główne błędy, które popełniają programiści na różnych etapach pracy z UIViewController.
Pierwszy błąd — tworzenie subviews w init lub loadView przy użyciu Storyboard. Jeśli używasz Interface Builder, nie nadpisuj loadView bez potrzeby. Tworzenie widoku w loadView przy istniejącej historyjce prowadzi do zignorowania pliku xib i pustego ekranu.
Drugi błąd — subskrypcja powiadomień klawiatury w viewDidLoad bez wypisywania się. Jeśli zasubskrybowałeś UIResponder.keyboardWillShowNotification, ale nie wypisałeś się przy ukrywaniu ekranu, blok będzie wywoływany nawet po deinit kontrolera — to wyciek pamięci z potencjalnym awarią aplikacji.
Trzeci błąd — timery i zapytania sieciowe uruchomione przed pojawieniem się ekranu. Ładowanie obrazów lub wykonywanie animacji, gdy widok nie jest jeszcze widoczny — marnowanie zasobów. Przenieś aktualizacje wizualne do viewWillAppear lub viewDidAppear.
Czwarty błąd — zapisywanie danych tylko w viewWillDisappear. Przy interaktywnym geście pop użytkownik może rozpocząć przesunięcie i anulować je — metoda została wywołana, ale ekran nie zniknął. Duplikuj krytyczne zapisywanie w viewDidDisappear lub w handlerze applicationDidEnterBackground.
Często zadawane pytania
Raz — po załadowaniu widoku do pamięci. Przy ponownych pojawieniach ekranu viewDidLoad nie jest wywoływane. Jeśli chcesz ponownie utworzyć widok, kontroler musi zostać zniszczony i utworzony od nowa.
UIKit wymaga wywołania super.viewDidLoad do poprawnego działania cyklu życia. Bez niego mogą wystąpić problemy z aktualizacją layoutu i obsługą przejść. Zawsze wywołuj super jako pierwszą rzecz w metodzie.
Nie jest zalecane. Jeśli kontroler jest inicjalizowany ze Storyboard, UIKit automatycznie ładuje widok z xib. Nadpisanie loadView anuluje ten proces, a twoja historyjka zostanie zignorowana.
Subskrybuj w viewDidLoad lub viewWillAppear, a wypisuj się w viewWillDisappear lub viewDidDisappear, używając słabej referencji do self, aby uniknąć wycieków pamięci przy domknięciach.
Force quit zabija proces przymusowo — UIKit nie zdąży wywołać metod Lifecycle. Do zapisywania danych użyj powiadomienia UIApplication.willTerminateNotification w AppDelegate.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również