ViewController Lifecycle은 UIKit이 iOS에서 화면을 관리할 때 자동으로 호출하는 메서드의 시퀀스입니다. Apple 문서에 따르면, 각 UIViewController는 View 생성부터 표시 및 숨김까지 예측 가능한 상태 집합을 거칩니다. 이해하는 것 이러한 메서드의 순서와 목적을 이해하는 것은 안정적인 iOS 앱 작동을 위한 필수 조건입니다.
주요 포인트
ViewController Lifecycle은 UIViewController가 존재 기간 동안 UIKit으로부터 받는 메서드의 집합입니다. iOS 앱의 각 화면은 생성, View 로딩, 화면 표시, 숨김, 메모리 해제 단계를 순차적으로 거칩니다. UIKit은 각 단계에서 해당 메서드를 자동으로 호출하고, 개발자는 자신의 로직을 추가하기 위해 이를 재정의합니다.
UIViewController 아키텍처는 UIKit의 기초이며 SwiftUI 시대에도 여전히 관련성이 있습니다 — 많은 프로젝트가 여전히 클래식 접근 방식이나 하이브리드 아키텍처를 사용합니다. Lifecycle을 이해하면 subviews가 언제 사용 가능한지, 레이아웃을 안전하게 수정할 수 있는 시점, 화면 표시 또는 숨김 시 어떤 작업을 수행해야 하는지 예측할 수 있습니다.
라이프사이클의 각 메서드에는 특정 목적이 있습니다: 일부는 컨트롤러의 전체 수명 동안 한 번만 호출되고, 다른 것들은 표시 또는 숨김 시마다 호출됩니다. 메서드 간에 로직을 혼합하면 메모리 누수, 잘못된 데이터 업데이트, 불필요한 네트워크 요청과 같은 찾기 어려운 버그가 발생합니다.
6개의 메서드가 UIViewController의 완전한 라이프사이클을 형성합니다. 호출 순서는 고정되어 있으며 탐색 방법(push, present, unwind segue)에 의존하지 않습니다 — 모두 동일한 일정을 따릅니다.
loadView는 사이클의 첫 번째 메서드로, 컨트롤러의 View가 아직 존재하지 않을 때 호출됩니다. Storyboard를 사용하는 경우 UIKit은 자동으로 xib 파일에서 View를 로드합니다. 프로그래밍 방식으로 인터페이스를 생성할 때는 이 메서드를 재정의하여 루트 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는 deinit이 호출되기 전의 마지막 Lifecycle 메서드입니다. 객체가 소멸되기 전의 최종 정리에 사용하세요.
호출 순서는 화면이 처음 표시되는지, 돌아갈 때인지, 모달로 표시되는지에 따라 다릅니다. 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 이해가 코드 품질과 사용자 경험에 직접 영향을 미치는 4가지 주요 시나리오를 살펴보겠습니다. 각 시나리오에 대해 권장 사항과 함께 예제를 제공합니다.
viewDidLoad는 화면 표시 여부에 의존하지 않는 초기 설정을 위한 장소입니다. 여기서 collectionView를 구성하고, 셀의 nib 파일을 등록하고, 데이터 소스와 레이아웃을 만듭니다. 네트워크에서 데이터를 로드하는 경우 viewDidLoad에서는 요청만 시작하고, 화면이 표시 준비가 된 viewWillAppear에서 UI를 업데이트하는 것이 좋습니다.
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는 사용자가 화면을 본 후 시작해야 하는 애니메이션을 시작하기에 이상적입니다. 여기서 분석 이벤트(화면 보기, 온보딩 시작, 비디오 재생 시작)도 보냅니다. 전환이 완료되기 전에 애니메이션을 시작하면 끊기는 인터페이스가 발생합니다 — UIKit이 충분한 프레임을 준비할 시간이 없습니다.
viewWillDisappear에서 드래프트를 저장하고, 타이머를 중지하고, NotificationCenter에서 구독을 해제합니다. 이는 화면이 아직 표시되고 사용자 컨텍스트가 필요한 작업에 액세스할 수 있는 마지막 순간입니다. 중요한 데이터의 경우 취소된 제스처에 대한 보호로 추가로 viewDidDisappear도 사용하세요.
라이프사이클 메서드의 잘못된 사용은 iOS 앱에서 버그의 가장 일반적인 원인 중 하나입니다. UIViewController 작업의 다양한 단계에서 개발자가 저지르는 주요 실수를 살펴보겠습니다.
첫 번째 실수 — Storyboard 사용 시 init 또는 loadView에서 subviews를 생성하는 것. Interface Builder를 사용하는 경우 불필요하게 loadView를 재정의하지 마세요. Storyboard가 있는 상태에서 loadView에서 View를 생성하면 xib 파일이 무시되고 빈 화면이 나타납니다.
두 번째 실수 — viewDidLoad에서 키보드 알림을 구독하고 구독 해제하지 않는 것. UIResponder.keyboardWillShowNotification을 구독했지만 화면 숨김 시 구독을 해제하지 않은 경우, 컨트롤러의 deinit 후에도 블록이 계속 호출됩니다 — 이는 앱 충돌 가능성이 있는 메모리 누수입니다.
세 번째 실수 — 화면이 표시되기 전에 타이머 및 네트워크 요청을 시작하는 것. View가 아직 표시되지 않았을 때 이미지를 로드하거나 애니메이션을 실행하는 것은 리소스 낭비입니다. 시각적 업데이트를 viewWillAppear 또는 viewDidAppear로 이동하세요.
네 번째 실수 — viewWillDisappear에서만 데이터를 저장하는 것. 대화형 pop 제스처를 사용하면 사용자가 스와이프를 시작하고 취소할 수 있습니다 — 메서드는 호출되었지만 화면은 사라지지 않았습니다. 중요한 저장을 viewDidDisappear 또는 applicationDidEnterBackground 핸들러에서 중복 수행하세요.
자주 묻는 질문
한 번 — View가 메모리에 로드된 후입니다. 화면이 다시 표시되어도 viewDidLoad는 호출되지 않습니다. View를 다시 생성해야 하는 경우 컨트롤러를 소멸하고 다시 생성해야 합니다.
라이프사이클이 올바르게 작동하려면 UIKit은 super.viewDidLoad 호출을 필요로 합니다. 이것이 없으면 레이아웃 업데이트 및 전환 처리에 문제가 발생할 수 있습니다. 메서드 내에서 항상 super를 먼저 호출하세요.
권장되지 않습니다. 컨트롤러가 Storyboard에서 초기화된 경우 UIKit은 자동으로 xib에서 View를 로드합니다. loadView를 재정의하면 이 프로세스가 취소되고 스토리보드가 무시됩니다.
viewDidLoad 또는 viewWillAppear에서 구독하고, viewWillDisappear 또는 viewDidDisappear에서 구독을 해제합니다. 클로저에서 메모리 누수를 방지하기 위해 self에 대한 약한 참조를 사용하세요.
강제 종료는 프로세스를 갑자기 종료합니다 — UIKit이 Lifecycle 메서드를 호출할 시간이 없습니다. 데이터를 저장하려면 AppDelegate에서 UIApplication.willTerminateNotification을 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.