예거's Bicycle for the mind

[iOS] ViewController 의 생명주기(Life Cycle) 위에 올라타기 본문

iOS & Swift

[iOS] ViewController 의 생명주기(Life Cycle) 위에 올라타기

유예거 2021. 11. 10. 16:35

화면이 메모리에 처음 올라가고(로딩), 사용자에게 시각적으로 보여지고, 그리고 다음 화면을 위해 자리를 양보하며 사라지고, 메모리에서 아예 해제되기도 하는 일련의 과정을 UIViewController Life Cycle 이라고 부릅니다.

 

풀네임을 자세히 보면, 그냥 ViewController 가 아니라 UIViewController 인 것을 확인할 수 있는데

그 이유는 생명주기의 모든 메서드는 애플이 제공하는 UIKit 내의 UIViewController 라는 클래스 안에 이미 정의가 되어있기 때문입니다.

 

우리는 UIViewController 클래스를 상속받은 뷰컨에서, 생명주기 메서드를 override 한 뒤에 사용하면 됩니다.

다시, UIViewController 클래스의 정의부 코드를 펼쳐놓고 살펴볼게요!

 

open class UIViewController: UIResponder, NSCoding, UIAppearanceContainer, UITraitEnvironment, UIContentContainer, UIFocusEnvironment {

    public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)

    public init?(coder: NSCoder)

    open var view: UIView!

    open func loadView()

    open func viewDidLoad()

    open func viewWillAppear(_ animated: Bool)

    open func viewDidAppear(_ animated: Bool)

    open func viewWillDisappear(_ animated: Bool)

    open func viewDidDisappear(_ animated: Bool)
}

 

지난 글에서는 UIViewController 의 2개의 이니셜라이저에 대해 알아봤어요.

이번 글에서는 view 프로퍼티와, 6개의 생명주기 메서드를 공부해볼 거예요.

 

 

위 이미지는 뷰컨의 생명주기를 도식화한 것입니다.

 

제일 중요한 사실은, 각 생명주기 메서드는 이전 단계가 다 완료되기 전에는 호출되지 않는다는 것이에요.

만약 viewDidLoad 메서드에서 수행해야 하는 작업에 3초가 걸린다면, 그 작업을 하는 3초 동안에는 viewWillAppear 메서드가 동작하지 않습니다.

 

그러면, 우리가 아이폰 앱에서 어떤 화면으로 진입한다고 생각해볼게요.

 

뷰컨은 사용자가 그 화면에 진입하려고 시도하기 전까지는 이니셜라이저를 호출하지 않아요. (인스턴스 안 만듦)

이 내용은 UIViewController 공식 문서에 더 자세하게 나와있어요.

 

View controllers load their views lazily. Accessing the view property for the first time loads or creates the view controller’s views.

 

처음엔 위 내용을 보고 view 프로퍼티가 레이지 프로퍼티(lazy property) 인가? 생각했는데요

클래스 정의부에 "lazy" 키워드가 없기 때문에, lazy 프로퍼티는 아닌 것으로 이해했습니다.

 

* 참고) 공식 문서의 lazy property 정의
A lazy stored property is a property whose initial value isn’t calculated until the first time it’s used.
You indicate a lazy stored property by writing the lazy modifier before its declaration.
// view 프로퍼티 정의
open var view: UIView!

// 만약 lazy 프로퍼티였다면...
open lazy var view: UIView!

 

1. init

앱에서 어떤 화면으로 진입할 때, 뷰컨은 이니셜라이저를 작동시켜 인스턴스를 생성합니다.

뷰컨은 2개의 이니셜라이저를 가지고 있고, 정리하면 아래와 같아요.

 

// nib file 을 통해 뷰컨의 인스턴스를 생성할 때 사용되는 이니셜라이저
public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)

// 스토리보드를 통해 뷰컨의 인스턴스를 생성할 때 사용되는 이니셜라이저
public init?(coder: NSCoder)

 

2. loadView

이 메서드는 뷰컨의 인스턴스 프로퍼티인 view 객체를 메모리에 올립니다.(load)

이 과정에서 @IBOutlet, @IBAction 들이 생성되고 실제 view 객체들과 자동으로 연결돼요.

애플의 공식 문서인 View Controller Programming Guide for iOS 에 써있는 내용입니다. (아래 노란 하이라이트)

 

그리고 loadView 메서드 공식 문서를 확인해보면, view 객체를 직접 코딩해서 만드는 경우가 아니라면, override 를 절대 하지 말라고 써있어요. (must not 이라는 강력한 표현이...)

 

그리고 뷰와 함께 추가적으로 이니셜라이즈를 해주고 싶은 게 있다면, viewDidLoad 메서드에서 하라고 써있어요.

 

If you use Interface Builder to create your views and initialize the view controller, you must not override this method. You can override this method in order to create your views manually.

If you want to perform any additional initialization of your views, do so in the viewDidLoad() method.

 

3. viewDidLoad

이 메서드가 호출되는 시점에서, 이미 view 객체는 메모리에 다 올라와 있어요.

loadView 메서드가 작업을 완료했기 때문에, viewDidLoad 가 호출됐다고 생각하면 쉬워요!

 

그리고 viewDidLoad 메서드는 뷰컨의 전체 생명주기에서 오직 한 번만 호출됩니다.

 

이게 무슨 말인지 비유를 들어보자면요, 우리가 보는 화면이 init ~ loadView ~ viewDidLoad 생명주기를 차근차근 완료하는 걸 "태어난다." 라고 볼 수 있다면

 

화면이 Navigation Controller 의 Stack 구조에서 pop 돼서 메모리에서 해제된 건 "죽는다." 라고 비유해볼 수 있어요.

 

Navigation Controller 는 Stack 구조를 갖는다.

 

즉, 사용자가 어떤 화면에 진입할 때, 뷰컨이 태어나고, 뒤로가기 버튼을 누르면 그 뷰컨은 죽어요. (메모리 할당 해제)

하지만 사용자가 다시 그 화면을 진입할 때, 뷰컨은 다시 태어납니다.

 

이처럼 처음 태어나거나, 혹은 죽고 다시 태어날 때만 호출되는 게 viewDidLoad 메서드입니다.

사실 init, loadView 또한 생명주기 전체에서 한 번만 호출되는 건 마찬가지긴 해요.

 

반면, 아래에서 배울 Appear, Disappear 이름이 달린 메서드들은 태어나고 죽기 전까지 여러 번 호출될 수 있어요.

 

어쨌든, viewDidLoad 메서드는 우리가 처음 뷰컨 파일을 만들면, 디폴트로 만들어져 있는 걸 확인할 수 있습니다.

 

 

주석으로 친절하게 "Do any additional setup after loading the view." 라고 써두기도 했네요.

그러니 viewDidLoad 메서드에는, 화면이 나타날 때 오직 한 번만 해주면 되는 작업을 넣어주면 좋아요!

 

4. viewWillAppear

이 메소드는 '미리 알림' 이라고 생각하면 쉬워요.

화면이 사용자가 볼 수 있도록 등장할 예정일 때 메서드가 호출돼요.

 

공식 문서의 정의에서도 "Notifies" 라는 단어가 사용됐어요.

뷰 계층의 최상단부에 곧 화면이 추가될 것이라고 미리 알림을 보내는 겁니다.

 

Notifies the view controller that its view is about to be added to a view hierarchy.

 

5. viewDidAppear

"나, 강림" 이라고 이해하면 쉬워요.

화면이 사용자에게 보여지도록 등장하고 나서 호출됩니다.

 

6. viewWillDisappear

다음 화면으로 전환되기 전에, 지금 보고 있는 화면이 '사라질 예정'이라는 걸 미리 알려주기 위해 호출됩니다.

이때, 사라진다는 건, 사용자의 눈에 안 보인다는 말이지, 메모리에서 해제됐다는 말이 아니에요!

뷰 계층에서 맨 위에 있다가, 새로운 화면이 쌓이면서 한 칸 아래로 내려갈 예정이라는 뜻입니다.

 

7. viewDidDisappear

화면이 완전히 사라진 뒤에 이 메서드가 호출돼요.

해당 뷰컨이 이제 뷰 계층 구조 아래에 깔리기 시작했다는 것이므로, 언제 이 화면으로 다시 돌아올지 알 수 없어요.

어떤 사용자는 바로 돌아올 수도 있지만, 어떤 사용자는 1시간 뒤에 이 화면으로 돌아올 수도 있는 거니까요.

 

그렇기 때문에, 백그라운드에서 불필요한 작업이 있다면, viewDidDisappear 메서드를 override 해서 멈춰주면 좋아요.

 

예를 들어, 사용자가 화면을 볼 수도 없는데, 다른 뷰컨에서 notification 을 받아서, 화면을 새로고침을 해줄 필요는 없으니까요!

 

// 뷰컨이 뷰 계층에서 깔려서 보이지 않는다면, 화면을 refresh 할 필요가 없다.
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(true)
    // notification 수신 중지
    // 새로고침 중지
}

 

다시 이 화면이 사용자에게 보여질 때, 그러니까 viewWillAppear 메서드가 다시 호출될 때

notification 수신을 다시 시작하고, 화면을 새로고침하는 게 더 효율적이라고 볼 수 있겠네요.

 

참고 링크

[애플 공식 문서] The Role of View Controllers

[애플 공식 문서] UIViewController

[군옥수수수 블로그] UIViewController Lifecycle

[Zedd 블로그] View Controller의 생명주기(Life-Cycle)

[Zedd 블로그] View의 생명주기2(Life-Cycle) / Navigation Controller

 

Comments