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 от екрана — при преминаване към друг контролер, затваряне на модален прозорец или минимизиране на приложението. Това е правилното място за запазване на състояние, отписване от известия, спиране на активни процеси и освобождаване на ресурси, които не са необходими, когато екранът не е видим.
Важно да запомните: viewWillDisappear не гарантира, че View в крайна сметка ще изчезне — жестът може да бъде отменен. Затова критични данни запазвайте и в viewDidDisappear, който се извиква само след действително изчезване.
viewDidDisappear завършва цикъла на появяване и изчезване. Извиква се след като View вече е скрито от екрана. В този метод анимациите окончателно се спират, временните обекти се премахват и се потвърждава запазването на данни, започнато в viewWillDisappear.
Този метод също предшества deinit на контролера — ако вашият UIViewController се унищожава, viewDidDisappear ще бъде последният метод на Lifecycle преди извикването на deinit. Използвайте го за окончателно почистване, което трябва да се случи преди унищожаването на обекта.
Редът на извикване зависи от това как точно се появява екранът: за първи път, при връщане назад или при модално показване. Нека разгледаме три основни сценария от гледна точка на 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 се пропускат, тъй като екранът вече съществува в навигационния стек.
Модално показване следва същите правила: при новия контролер се извиква пълният цикъл при първата поява, а при текущия — 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 или в handler-а на applicationDidEnterBackground.
Често задавани въпроси
Веднъж — след зареждане на View в паметта. При повторни появявания на екрана viewDidLoad не се извиква. Ако трябва да пресъздадете View, контролерът трябва да бъде унищожен и създаден наново.
UIKit изисква извикване на super.viewDidLoad за коректна работа на жизнения цикъл. Без него са възможни проблеми с обновяване на layout и обработка на преходи. Винаги извиквайте super като първо нещо в метода.
Не се препоръчва. Ако контролерът е инициализиран от Storyboard, UIKit автоматично зарежда View от xib. Презаписването на loadView отменя този процес и вашият storyboard ще бъде игнориран.
Абонирайте се в viewDidLoad или viewWillAppear и се отпишете в viewWillDisappear или viewDidDisappear, като използвате слаба референция към self, за да избегнете изтичане на памет при затваряния.
Force quit принудително убива процеса — UIKit не успява да извика методите на Lifecycle. За запазване на данни използвайте известието UIApplication.willTerminateNotification в AppDelegate.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също