ViewController — iOS 화면 컨트롤러의 본질과 Lifecycle

저자: IT Sectr 게시일: 2026-02-22 읽는 시간: 7 분

UIViewController는 iOS 애플리케이션의 핵심 클래스로, 화면과 그 내용을 관리합니다. 모든 iPhone 또는 iPad 화면은 하나의 ViewController에 의해 관리되며, 표시, 생명주기 및 탐색을 조정합니다. UIKit 아키텍처에 대한 자세한 내용은 Apple 공식 문서를 참조하세요.

핵심 포인트

  • UIViewController — UIKit에서 고유한 생명주기를 가진 화면 관리 기본 클래스
  • viewDidLoad — 한 번 호출되며, UI 초기화 및 데이터 구독 지점
  • viewWillAppear — 화면이 곧 표시되며, 표시 전 데이터 업데이트
  • Lifecycle에는 다섯 가지 메서드 포함: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear
  • Massive View Controller — iOS의 주요 안티패턴으로, MVVM 또는 Coordinator로 해결

ViewController란?

UIViewController는 UIKit 프레임워크의 클래스로, UIView 계층 구조를 관리하고 화면에 데이터 표시를 조정합니다. 모든 iOS 애플리케이션에는 최소 하나의 ViewController(창의 루트 컨트롤러)가 포함됩니다. 컨트롤러는 화면 회전, 화면 간 전환 및 생명주기 이벤트를 처리합니다.

iOS의 MVC(Model-View-Controller) 아키텍처는 UIViewController를 통해 정확하게 구현됩니다. 컨트롤러는 모델로부터 데이터를 받아 뷰를 업데이트합니다. ViewController는 시각적 요소가 아닙니다. view 속성을 관리하며, 여기에는 subview의 계층 구조가 포함됩니다. Apple(2026)에 따르면 UIKit에는 40개 이상의 내장 UIViewController 하위 클래스가 있습니다.

첫 번째 iPhone SDK(2008)에는 세 가지 생명주기 메서드가 있는 UIViewController가 포함되었습니다. 18년 동안 Apple은 Container View Controller, 적응형 프레젠테이션, 사용자 정의 애니메이션을 위한 UIViewControllerTransitioningDelegate 및 iPad의 분할 화면 모드 지원을 추가했습니다. UIViewController는 UIKit 애플리케이션에 필수적인 구성 요소로 남아 있습니다.

UIViewController의 생명주기

UIViewController의 생명주기는 화면 생성, 표시 및 숨김 시 시스템에 의해 호출되는 메서드의 시퀀스입니다. 생명주기를 이해하는 것은 매우 중요합니다. 잘못된 코드 배치는 메모리 누수, 불필요한 네트워크 요청 및 인터페이스 깜박임을 초래합니다.

메서드호출 시점목적
viewDidLoad한 번, view가 메모리에 로드된 후초기 UI 설정, Combine 구독
viewWillAppear화면이 나타나기 전데이터 업데이트, 네비게이션 바 숨기기/표시
viewDidAppear화면이 나타난 후애니메이션 시작, 분석, 카메라 업데이트
viewWillDisappear화면을 떠나기 전초안 저장, 알림 구독 취소
viewDidDisappear화면을 떠난 후무거운 프로세스 중지, 리소스 해제

화면 표시 시 호출 순서

화면이 처음 표시될 때 시퀀스: init → loadView → viewDidLoad → viewWillAppear → viewDidAppear. 다시 표시될 때(다른 화면에서 돌아올 때): viewWillAppear → viewDidAppear. viewDidLoad는 컨트롤러의 수명 동안 한 번만 호출됩니다.

viewDidLoad, init 및 UI 설정

viewDidLoad 메서드는 사용자 인터페이스를 구성하는 주요 지점입니다. view가 메모리에 로드된 후, 모든 IBOutlet 연결이 이미 설정되었을 때 호출됩니다. 여기서 UI 요소가 프로그래밍 방식으로 생성되고, 제약 조건이 구성되며, 초기 데이터가 로드됩니다.

swift
final class ProfileViewController: UIViewController {

    private let tableView = UITableView()
    private let viewModel = ProfileViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        bindViewModel()
    }

    private func setupUI() {
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        tableView.register(ProfileCell.self,
                         forCellReuseIdentifier: ProfileCell.reuseId)
    }

    private func bindViewModel() {
        viewModel.$user
            .receive(on: DispatchQueue.main)
            .sink { [weak self] user in
                self?.title = user.name
            }
            .store(in: &cancellables)
    }
}

SwiftUI에서 이 코드는 View의 body와 동일합니다. 그러나 UIViewController는 생명주기와 최적화에 대한 완전한 제어를 제공합니다. bindViewModel은 반응형 구독에 Combine을 사용합니다. 모델이 변경되면 데이터가 자동으로 업데이트됩니다.

viewWillAppear 및 데이터 업데이트

viewWillAppear는 화면이 이미 메모리에 있더라도 표시되기 전에 매번 호출됩니다. 이는 다른 화면에서 변경되었을 수 있는 데이터를 업데이트하는 곳입니다. 목록 다시 로드, 알림 배지 업데이트, 특정 화면에 대한 네비게이션 바 구성 등을 수행합니다.

swift
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // 이 화면에서 네비게이션 바 숨기기
    navigationController?.setNavigationBarHidden(true, animated: animated)

    // 다른 화면에서 돌아올 때 데이터 업데이트
    tableView.reloadData()
    badgeLabel.text = "\(CartManager.shared.itemCount)"
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // 분석: 사용자가 화면을 본 후에만
    AnalyticsService.shared.logScreenView("Profile")
}

viewDidLoad와 viewWillAppear의 차이는 중요합니다. viewDidLoad는 한 번 실행되며 정적 설정에 적합하고, viewWillAppear는 화면이 표시될 때마다 실행되며 동적 업데이트에 적합합니다. viewDidLoad에 네트워크 요청을 배치하면 화면으로 돌아올 때 오래된 데이터가 표시됩니다.

Container View Controller: UINavigationController 및 UITabBarController

Container View Controller는 하나 이상의 자식 ViewController를 관리하는 ViewController입니다. Apple은 세 가지 내장 컨테이너를 제공합니다: UINavigationController(화면 스택), UITabBarController(탭), UISplitViewController(iPad용 마스터-디테일).

UINavigationController는 스택에서 전환을 구성합니다. push는 화면을 추가하고 pop은 제거합니다. UITabBarController는 애플리케이션의 독립적인 섹션 간을 전환합니다. UISplitViewController는 iPad에서 두 개의 컨트롤러를 나란히 표시하고 iPhone에서는 하나를 표시합니다. 개발자는 addChild를 통해 사용자 정의 컨테이너를 만들 수 있습니다.

swift
// 사용자 정의 Container View Controller
final class ContainerViewController: UIViewController {

    private let sidebarVC = SidebarViewController()
    private let contentVC = ContentViewController()

    override func viewDidLoad() {
        super.viewDidLoad()

        // 자식 컨트롤러 추가
        addChild(sidebarVC)
        view.addSubview(sidebarVC.view)
        sidebarVC.didMove(toParent: self)

        addChild(contentVC)
        view.addSubview(contentVC.view)
        contentVC.didMove(toParent: self)
    }
}

Container View Controller를 올바르게 사용하려면 addChild, view 추가 및 didMove(toParent:)를 이 순서로 호출해야 합니다. 제거 시: willMove(toParent: nil), removeFromSuperview, removeFromParent. 순서를 위반하면 메모리 누수가 발생합니다.

MVVM 및 Coordinator로 Massive View Controller 해결

Massive View Controller 문제는 UIViewController에 비즈니스 로직, 네트워크 요청, 탐색 및 UI 코드의 수백 줄이 포함될 때 발생합니다. Apple은 이 문제를 인식하고 탐색을 추출하기 위해 Coordinator와 함께 MVVM(Model-View-ViewModel)을 권장합니다.

MVVM은 비즈니스 로직을 컨트롤러에서 ViewModel로 이동합니다. Controller는 Combine 또는 델리게이트를 통해 ViewModel을 View에 바인딩만 합니다. Coordinator는 탐색 로직(컨트롤러 간 생성 및 전환)을 별도의 클래스로 추출합니다. 이 접근 방식은 2024년부터 Apple 모범 사례에 채택되었습니다.

swift
// Coordinator — 탐색 관리
protocol Coordinator {
    var childCoordinators: [Coordinator] { get set }
    func start()
}

final class MainCoordinator: Coordinator {

    var childCoordinators = [Coordinator]()
    private let navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func start() {
        let vc = ListViewController()
        vc.didSelectItem = { [weak self] item in
            self?.showDetail(item)
        }
        navigationController.pushViewController(vc, animated: false)
    }

    private func showDetail(_ item: Item) {
        let vc = DetailViewController(item: item)
        navigationController.pushViewController(vc, animated: true)
    }
}

UIViewController vs SwiftUI: 언제 무엇을 선택할까

UIViewController와 SwiftUI View 사이의 선택은 프로젝트 시작 연도, 사용자 정의 요구 사항 및 최소 지원 iOS 버전에 따라 다릅니다. UIKit과 UIViewController는 2020년 이전에 시작된 프로젝트와 깊은 인터페이스 사용자 정의가 필요한 애플리케이션의 기반으로 남아 있습니다.

SwiftUI는 iOS 17+의 새로운 프로젝트, 표준 인터페이스 및 프로토타입에 적합합니다. 그러나 사용자 정의 전환, 카메라 작업, MapKit, 복잡한 CALayer 애니메이션에는 UIViewController가 필요합니다. Apple은 UIHostingController(UIKit 내의 SwiftUI)와 UIViewRepresentable(SwiftUI 내의 UIKit)을 통해 접근 방식을 결합할 것을 권장합니다.

시나리오UIKit (UIViewController)SwiftUI (View)
사용자 정의 애니메이션UIViewPropertyAnimator를 통한 완전한 제어Animation을 통한 제한적
카메라 작업AVCaptureSession + UIViewPreviewUIViewControllerRepresentable을 통해
CollectionViewUICollectionView + UICollectionViewLayoutLazyVGrid/LazyHGrid
iPad 적응UISplitViewController + UITraitCollectionNavigationSplitView + sizeClass
개발 속도느림 (수동 레이아웃)빠름 (선언형)

자주 묻는 질문

UIViewController와 UIView는 어떻게 다른가요?

UIViewController는 화면과 그 생명주기를 관리하는 컨트롤러입니다. UIView는 콘텐츠를 표시하는 뷰입니다. ViewController는 UIViews의 계층 구조를 포함하지만 자체는 시각적 요소가 아닙니다. 하나의 컨트롤러가 여러 뷰를 관리합니다.

Massive View Controller란 무엇인가요?

Massive View Controller는 UIViewController에 데이터, 탐색, 네트워크 요청, 애니메이션 등 너무 많은 로직이 포함된 안티패턴입니다. 해결책은 코드를 별도의 서비스, 코디네이터 및 ViewModel(MVVM)로 추출하는 것입니다.

ViewControllers 간에 데이터를 전달하는 방법은?

네 가지 방법: prepare(for:sender:)(Segue)의 속성을 통해, 델리게이트(Delegate)를 통해, 클로저(Closure)를 통해, 공유 서비스를 통해. 느슨한 결합을 위해 Coordinator + Delegate 또는 Combine을 사용하세요.

Container View Controller란 무엇인가요?

Container View Controller는 자식 ViewController를 관리하는 컨트롤러입니다. 예: UINavigationController, UITabBarController, UISplitViewController. 부모 컨트롤러는 addChild를 통해 자식을 추가하고, 이들 사이를 전환하며 레이아웃을 관리합니다.

SwiftUI View 대신 UIViewController를 언제 사용해야 하나요?

UIViewController — 복잡한 사용자 정의 애니메이션, 카메라 작업, 지도, 비디오, 사용자 정의 레이아웃의 UICollectionView에 사용. SwiftUI View — iOS 13+의 표준 인터페이스에 사용. UIHostingController를 통한 결합은 허용됩니다.

요약

  • UIViewController — 화면, UIView 계층 구조 및 생명주기 관리를 위한 중앙 UIKit 클래스
  • Lifecycle은 다섯 가지 메서드로 구성: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear
  • viewDidLoad — 초기 UI 설정 지점, 컨트롤러 수명 동안 한 번 호출
  • viewWillAppear — 표시 전에 매번 호출, 데이터 업데이트 및 네비게이션 바 구성에 적합
  • Container View Controller(UINavigationController, UITabBarController)는 자식 컨트롤러의 계층 구조를 관리
  • Massive View Controller는 MVVM(ViewModel로 로직 추출) 및 Coordinator(탐색 추출)로 해결
  • UIViewController와 SwiftUI는 하이브리드 애플리케이션을 위해 UIHostingController 및 UIViewRepresentable을 통해 결합 가능

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기