[TIL] ViewController LifeCycle

Valse·2022년 8월 24일
0

iOSInterViewController

목록 보기
4/8
post-thumbnail

생명주기

iOSInterViewController 시리즈에서 다뤘던 델리게이트 패턴과 NotificationCenter를 활용한 옵저버 패턴은 객체와 객체 사이의 소통을 가능하게 해주었다.

그러나 뷰 컨트롤러 객체는 사라질 수 있다.
뷰 컨트롤러가 사라진 다음에 소통을 하려고 하면 잘 되지 않을 것이다.
따라서 어떤 상황에 어떻게 객체 간 소통을 유연하게 할 수 있을지 정하기 위해선 뷰 컨트롤러 객체의 생명주기를 조금이라도 이해할 필요가 있다.

코딩의 유연함을 위해 하나의 뷰 컨트롤러가 너무 많은 책임을 갖고 있는 것은 좋지 않다.
비대한 하나의 뷰 컨트롤러 만으로 앱을 만들 수야 있겠으나, 단일책임원칙에 따르지 않은 코드가 받는 평가는 박하다.
그렇기 때문에 뷰 컨트롤러는 서로 다른 뷰를 그리거나 관리하기 위해 각각 역할을 나누어서 정의되고, 앱의 상황에 따라 메모리에서 유지되는 뷰 컨트롤러 또한 변화할 수 있다.
이러한 객체 변화의 알맞은 시기에 데이터 소통을 해야 메모리에서 이어받는 다른 뷰 컨트롤러 객체가 올바르게 작동할 것이다.

뷰 컨트롤러 객체가 관리하는 뷰가 화면에 로드 되었는지, 사라질 예정인지, 메모리에서 내려갔는지 등등 상황에 따라 호출되는 내부 메소드들이 있다.
그렇다면 구체적으로 어떤 일들이 일어나고 있고, 그 상황을 어떻게 활용할 수 있을지 알아보자.


활용

아래의 내용은 Understanding the iOS ViewController Life Cycle 을 참고하여 작성했습니다.

1. init()

뷰 컨트롤러를 스토리보드로 구성한다면, 그 구성 시점에 init(coder:) 생성자가 호출된다.
이 시점에서 뷰 컨트롤러가 자신의 생명주기를 유지하기 위해 필요로 하는 재료들이 메모리에 할당될 수 있다.
모든 객체가 그러하듯, init(coder:) 는 단일 인스턴스에 대해 단 한번만 호출된다.
코드로 UI를 구성한다면 init() 생성자의 커스터마이징이 가능해진다는 장점을 얻는다.
이전에 설명했던 Dependency Injection 또한 가능하기 때문에 활용 여지가 많은 시점이다.

2. viewDidLoad()

이제 뷰 컨트롤러가 생성되었으니 View를 로드하면 된다.
그러나 root View가 이미 설정되어 있는 경우가 있다.
UINavigationController에서 rootViewController를 설정할 수 있는데, 이 루트 뷰 컨트롤러에서 다른 뷰 컨트롤러로 화면을 넘긴다면 이전 뷰 컨트롤러는 뷰를 그린 채로 메모리에 남아있게 된다
그래서 루트 뷰 컨트롤러로 돌아오게 되면 viewDidLoad()가 호출되지 않는다.

viewDidLoad()는 뷰 컨트롤러의 subViewrootView가 로드된 후에 단 한번만 호출된다.
실제 앱을 만들 때는 override하여 쓰는 경우가 많은데, 재정의를 활용하여 로드되는 뷰의 세부 설정을 할 수 있다.
이 생명주기 단계에서는 뷰가 생성되고, 스토리보드와 코드가 잘 연결되었음을 확인할 수 있다.
이 단계는 백그라운드 작업을 시작하기에도 꽤 적절하다.
한 번만 불러와도 되는 경우에 국한하여, 네트워크 통신으로 유저 정보를 가져올 수도 있고 그외 다양한 활용이 가능할 것이다.

2.1. loadView()

이 특별한 메소드는 viewDidLoad() 이전 단계에 호출된다.
코드로 UI가 구성된 메인 뷰를 만들어내서 호출한다.
이 경우, loadView()를 재정의해야 하는데, 이 때는 super.loadView()은 구현해선 안 된다.
따라서 스토리보드로 UI를 그리신다면 굳이 들여다볼 필요가 없는 메소드가 되시겠다.

3. viewWillAppear()

뷰가 화면에 실제로 보여지기 전에 호출된다.
뷰 컨트롤러가 화면에 등장할 때마다 어떤 작업을 해야한다면 이 메소드를 재정의해서 활용할 수 있다.
단 한번만 호출되던 여러 생명주기 메소드와 달리 이 메소드는 여러차례 호출되기 때문에 단 한번만 작업해야 하는 작업이 이 메소드에서 정의될 필요는 없는 것이다.
다른 화면으로 넘어가 있던 동안 변화하는 유저의 데이터를 활용하여 UI를 새로 조정해야 할 때도 활용할 수 있다.

4. viewDidAppear()

뷰가 화면에 실제로 보여진 다음 호출된다.
음악이나 영상을 재생한다거나, 네트워크의 데이터를 모아온다거나 하는 작업을 진행하기에 좋다.
서버에 데이터 리퀘스트를 하거나, 코어데이터에 접근하여 필요한 데이터를 로드하는 등의 작업이 이루어지기에 적합하다.

5. viewWillDisappear()

뷰가 생겨났으니, 이제 뷰가 사라지는 경우도 살펴보자.
이 단계에서의 메소드는 현재의 뷰가 다른 화면으로 넘어가기 전에 호출된다.
viewWillDisappear() 는 특정 필요에 의한 작업을 실행하기 위해서만 재정의되곤 한다.

6. viewDidDisappear()

뷰가 실제로 사라지고 난 후에는 viewDidDisappear()가 호출된다.
이 메소드는 뷰가 사라진 후 '멈춰야 할 작업'이 있다면 재정의해서 사용하곤 한다.
viewDidAppear() 에서 실행되었던 음성이나 영상을 멈추고, 백그라운드에서 진행하는 작업들 중 멈춰야 하는 작업들을 멈추면 된다.

7. deinit()

메모리에서 완전히 해제된 후에는 deinit()이 호출된다.
의도치 않은 객체 간 강한 참조로 RC가 0이 되지 않는다면 당연히 deinit()는 호출되지 않으니, 각 뷰 컨트롤러 간의 의도치 않은 참조 관계를 잘 파악해야 한다.
delegate 패턴을 활용하는 상황에서 참조 선언이 강하게 된다거나...


정리

  • 여담
    뷰 컨트롤러의 생명주기와 마찬가지로 화면의 생명주기도 존재한다.
    런루프와 업데이트 사이클, 화면을 새로 그리는 주사율 등등에 따라 화면은 특정 시점에만 main 쓰레드에서만 업데이트 된다던가, pushpresent 가 화면 생명주기에 끼치는 변화라던가, 원하는 시점에 강제로 화면을 다시 그리게 한다던가...
    이건 추후에 따로 정리가 이루어질 예정이다.
  1. 뷰 컨트롤러의 생명주기는 뷰의 로드 시점과 화면 이동 시점에 따라 여러 단계로 나뉜다.
  2. 만약 루트 뷰 컨트롤러가 이미 존재한다면, 해당 루트 뷰 컨트롤러에서 Navigate되는 뷰 컨트롤러로의 이동이 이전 루트 뷰 컨트롤러를 사라지게 하지 않는다.
  3. 각 생명주기단계에 적절한 메소드를 구현하고 활용하여 UI/UX에 적합한 앱을 제작할 때에 도움이 된다.
  4. 메모리 관리 측면에서 각 객체의 생명주기에 대한 이해가 도움이 된다.
  5. 각 뷰 컨트롤러 간의 화면 이동에서 호출되는 각각의 생명주기 메소드는 뷰 컨트롤러의 상태에 따라 순서가 달라질 수 있다.

참고자료

Understanding the iOS ViewController Life Cycle <- 일독 강력 추천

220824

profile
🦶🏻🦉(발새 아님)

0개의 댓글