티스토리 뷰

결론: 화면 전환은 뷰가 메모리에 모두 로드 되고, viewDidLoad() 메소드의 호출이 완료돼야 일어난다. 뷰가 메모리에 로드되는 시점은 실제로 화면 전환을 하기 위해 present(_:animated:) 메소드를 호출하거나 세그웨이를 실행하는 등의 행위를 할 때(이때 뷰 컨트롤러가 뷰를 메모리에 로드한다)이고 그래서 viewDidLoad() 메소드도 이때 호출된다.


뷰 컨트롤러 사이에 값을 전달하는 걸 실습해보면서 viewDidLoad() 메소드를 오버라이딩해서 사용했는데 문득 이 메소드가 호출되는 시점이 정확히 언제인지가 궁금해졌다. instantiateViewController(withIdentifier:) 메소드를 호출하여 뷰 컨트롤러가 인스턴스화된 순간인지, 아니면 화면을 전환하는 그 순간인지(present(_:animated:) 메소드를 호출하거나 세그웨이가 실행되는 순간). 그래서 간단하게 실험을 해보기로 했다.

실험 - present(_:animated:)와 액션 세그웨이로 화면전환

실험으로 언제 화면이 메모리에 모두 로드되는지, 그리고 실제로 유저가 보는 화면 전환 애니메이션은 언제 일어나는지를 알아보기 위한 실험을 진행해보았다.

먼저 스토리보드를 다음과 같이 준비했다.

스토리보드

두 개의 뷰 컨트롤러에는 화면을 구분하기 위해 레이블을 각각 달아두었고, 루트 뷰 컨트롤러에는 Present와 Action Segue 버튼을 만들어두었다. Present 버튼은 @IBAction으로 연결하여 프리젠테이션 화면 전환을 하고 Action Segue는 버튼을 트리거로 하는 액션 세그웨이를 실행한다.

루트 뷰 컨트롤러 코드

먼저 루트 뷰 컨트롤러의 클래스 코드를 살펴보자

//
//  ViewController.swift
//  viewDidLoadTest
//
//  Created by 정성훈 on 2020/12/27.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func onPresent(_ sender: Any) {
        guard let svc = self.storyboard?.instantiateViewController(withIdentifier: "SVC") else {
            return
        }
        NSLog("svc 인스턴스화 완료. 3초 대기")
        sleep(3)

        NSLog("present 메소드 호출")
        self.present(svc, animated: true)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        NSLog("세그웨이 실행. 3초간 대기")
        sleep(3)
    }

}

먼저 onPresent(_:) 메소드를 보자. 이 메소드는 화면 전환을 할 뷰 컨트롤러 "SVC"를 인스턴스화 하고 3초를 대기하였다가, present(_:animated:) 메소드를 호출한다. 전환될 뷰 컨트롤러의 viewDidLoad() 메소드에 적절한 코드를 삽입해두어 언제 화면이 메모리에 전부 로드되는지를 확인할 수 있도록 하였다.

다음으로 prepare(for:sender:) 메소드를 보자. 이 메소드는 단순히 세그웨이가 실행되기 전에 호출되는 메소드로, 세그웨이가 실행되는 시점과 화면이 메모리에 다 올라온 시점을 비교하기 위해 로그를 찍어두었다.

화면이 전환될 두 번째 뷰 컨트롤러

이번엔 화면이 전환될 두 번째 뷰 컨트롤러와 연결된 클래스 파일을 보자.

import UIKit

class SecondViewController: UIViewController {
    override func viewDidLoad() {
        NSLog("SecondViewController의 viewDidLoad 호출. 이후 3초간 대기")
        sleep(3)

        NSLog("viewDidLoad 호출 완료")
    }
}

viewDidLoad() 메소드를 오버라이드하여 작성했다. 언제 이 메소드가 호출되는지를 알아보기 위해 로그를 찍고 실제 화면 전환이 어떻게 이루어지는지를 확인하기 위해 3초를 기다렸다.

실험 결과

실험 결과, viewDidLoad() 메소드는 실제로 화면이 전환되는 메소드가 호출되거나 세그웨이가 실행되는 그 순간 호출되는 것을 알 수 있었다. 즉 화면이 전환되는 순간에 메모리에 로드된다는 의미이다. 또 viewDidLoad() 호출이 마무리가 되어야 실제로 화면의 전환이 일어난다는 것을 알 수 있었다.

먼저 프리젠테이션 방식으로 화면전환을 할 때 로그는 다음과 같다.

프리젠테이션 방식 로그

viewDidLoad 가 호출된 후 3초가 지난 후에야 화면이 전환된 것을 보아 화면 전환은 이 메소드의 호출이 끝난 후 일어난다는 것을 알 수 있다.

다음으로 액션 세그웨이 방식으로 화면 전환을 할 때 로그는 다음과 같다.

세그웨이 방식 로그

역시 viewDidLoad() 메소드가 호출 완료 되고 화면이 전환되었다.

 

실험의 결론

'화면이 메모리에 모두 로드된다'는 것은 소스 코드 상에서 인스턴스화 했을 때가 아니라, 실제로 화면 전환이 일어나는 그 순간에 로드되는 것이다. 그래서 viewDidLoad() 메소드는 이 순간에 호출된다. 또 viewDidLoad() 메소드의 호출이 완료되어야 실제로 화면 전환이 완료된다.

공식문서로 보충

실험만으로는 결론이 애매하다. 그래서 공식문서를 찾아들어갔다.


Apple Developer Documentation

 

Apple Developer Documentation

 

developer.apple.com

공식문서에 따르면, viewDidLoad() 메소드는 '뷰 컨트롤러가 뷰 계층구조를 메모리에 로드한 후'에 호출된다고 한다.

 

뷰 컨트롤러를 인스턴스화했을 때 왜 viewDidLoad() 가 호출되지 않는 이유는 바로 view가 아닌 view controller를 메모리에 로드했기 때문이다. 뷰 컨트롤러와 뷰는 다르다. 뷰 컨트롤러는 루트 뷰와 뷰의 계층 구조를 통해서 뷰들을 관리하는 역할이고 뷰는 실제로 화면의 콘텐츠를 채우는 역할을 한다. 그래서 뷰 컨트롤러가 인스턴스화된 순간에는 viewDidLoad() 가 호출되지 않았던 것이다. 뷰가 메모리에 로드되는 순간은 바로 화면이 전환되는 그 순간이다.

 

instantiateViewController(withIdentifier:) 를 호출했을 때 viewDidLoad() 가 호출되지 않았던 이유는 명확했다. 뷰가 아닌 뷰 컨트롤러가 메모리에 로드되었기 때문. 의외로 간단하고 당연한 결과다. 🤭.

 

 


시간이 좀 지나고 이 글의 보충을 하자면, viewcontroller life cycle과 연계하여 알면 더 좋을 것 같다.

1. Instantiated (보통 스토리보드로부터 초기화됨)

2. awakeFromNib

3. segue preparation happens

4. outlets get set (아웃렛이 여기서 설정됨. 그래서 segue preparation 단계에서는 아웃렛을 참조 못함)

5. viewDidLoad

6. viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisAppear는 상황에 따라 호출됨

7. 'geometry changed'가 발생할 때마다, viewWillLayoutSubviews 호출 후 오토레이아웃 적용, 이후 viewDidLayoutSubviews 호출(이 단계는 언제 호출될지 확정지을 수 없음. 내부적으로 호출하는 데 정확한 시점을 모름)

8. 메모리 부족해지면 didReceiveMemoryWarning 호출

댓글