[iOS 7주차] ViewController 생명주기

DoyleHWorks·2024년 12월 2일
1

What I've Learned:

iOS: ViewController 생명주기

iOS에서 ViewController의 생명주기는 화면(ViewController)이 생성되고 소멸될 때까지의 일련의 단계로 이루어진다. 이를 통해 ViewController가 화면에 표시되기 전후로 적절히 준비하거나, 정리 작업을 수행할 수 있다. 주요 생명주기 메서드와 함께 단계별로 설명하면 다음과 같다.


1. 초기화 단계

init(coder:) 또는 init(nibName:bundle:)

  • ViewController가 초기화될 때 호출됨
  • 의존성 주입이나 데이터 모델 초기화 같은 작업을 수행함

2. 뷰 계층 구조 로드

loadView()

  • ViewController의 view가 생성될 때 호출됨
  • 기본적으로 스토리보드나 XIB에서 정의된 뷰를 로드하지만, 코드로 뷰를 직접 생성하려면 이 메서드를 재정의해야함

viewDidLoad()

  • view가 메모리에 로드된 후 호출됨
  • 뷰 초기화 작업(예: UI 요소 설정, 데이터 바인딩)을 수행함
  • 뷰 컨트롤러 생명주기에서 한 번만 호출됨

3. 뷰가 화면에 표시되기 전

viewWillAppear(_:)

  • 뷰가 화면에 표시되기 직전 호출됨
  • 데이터 갱신, 상태 복원, 뷰 레이아웃 변경 등을 수행함
  • 화면이 표시될 때마다 반복적으로 호출됨

4. 뷰가 화면에 표시되는 중

viewIsAppearing(_:) (iOS 13 이상)

  • 뷰 컨트롤러의 뷰가 실제로 뷰 계층 구조에 추가된 후 호출됨
  • 뷰가 화면에 표시되는 중에 추가 작업을 수행할 수 있음
  • viewWillAppear(_:)viewDidAppear(_:) 사이의 동작을 보완함
사용 예시
override func viewIsAppearing(_ animated: Bool) {
    super.viewIsAppearing(animated)
    print("The view is currently appearing on the screen.")
}

5. 뷰가 화면에 완전히 표시된 후

viewDidAppear(_:)

  • 뷰가 화면에 완전히 표시된 후 호출됨
  • 뷰가 완전히 준비된 이후 실행해야 하는 작업(예: 애니메이션 시작, 데이터 로드 등)을 수행함

6. 뷰가 화면에서 사라지기 전

viewWillDisappear(_:)

  • 뷰가 화면에서 사라지기 직전 호출됨
  • 입력 중지, 네트워크 요청 취소 등 화면을 떠나기 전에 필요한 작업을 수행함

7. 뷰가 화면에서 완전히 사라진 후

viewDidDisappear(_:)

  • 뷰가 화면에서 완전히 사라진 후 호출됨
  • 리소스 정리, UI 초기화, 상태 저장 등의 작업을 수행함

8. 메모리 경고 처리

didReceiveMemoryWarning()

  • 메모리 부족 경고가 발생했을 때 호출됨
  • 캐시 데이터 정리나 불필요한 메모리 해제 작업을 수행함
  • (iOS 13 이상에서는 잘 사용되지 않음)

9. 뷰 소멸

deinit

  • ViewController가 메모리에서 해제될 때 호출됨
  • 옵저버 제거, 리소스 해제 등 클린업 작업을 수행함

생명주기의 주요 흐름 요약

  1. 초기화 단계: init(coder:)loadView()viewDidLoad()
  2. 화면 표시 단계:
    • 표시 전: viewWillAppear(_:)viewIsAppearing = true
    • 표시 중: viewIsAppearing(_:) (iOS 13 이상)
    • 표시 후: viewDidAppear(_:)viewIsAppearing = false
  3. 화면 사라짐 단계:
    • 사라지기 전: viewWillDisappear(_:)
    • 사라진 후: viewDidDisappear(_:)
  4. 메모리 관리 단계: didReceiveMemoryWarning() (OLD) → deinit



loadView()

loadView()는 기본적으로 스토리보드나 XIB 파일에서 뷰를 자동으로 로드하지만, 코드 기반으로 UI를 작성할 때 직접 재정의하여 뷰 계층 구조를 생성해야 한다.


loadView()의 역할

  • 뷰 컨트롤러의 기본 뷰(view 속성)를 생성하는 메서드
  • 스토리보드 또는 XIB를 사용하는 경우 자동으로 뷰를 로드하기 때문에 재정의할 필요가 없음
  • 코드로 UI를 작성할 때는 이 메서드를 재정의하여 뷰를 수동으로 생성하고 설정함

언제 재정의해야 할까?

  • 스토리보드나 XIB를 사용하지 않는 경우 (즉, 코드베이스로 UI를 작성할 때).
  • 뷰 컨트롤러가 사용하는 루트 뷰를 완전히 커스터마이징해야 할 때.

재정의 예시

1. 기본 코드 작성

loadView()를 재정의하여 뷰 계층 구조를 코드로 생성함:

override func loadView() {
    // 기본 뷰 생성
    let rootView = UIView()
    rootView.backgroundColor = .white

    // UI 요소 추가
    let label = UILabel()
    label.text = "Hello, World!"
    label.textAlignment = .center
    label.frame = CGRect(x: 50, y: 100, width: 200, height: 50)
    
    rootView.addSubview(label)
    
    // ViewController의 view에 설정
    self.view = rootView
}

2. UIKit 요소를 더 세부적으로 설정하는 경우

뷰 계층 구조를 더 세부적으로 설정할 수도 있음:

override func loadView() {
    let customView = CustomView() // 커스텀 뷰 생성
    self.view = customView
}

주의 사항

  • super.loadView()를 호출하면 안 됨

    • loadView()뷰를 생성하는 책임이 개발자에게 있다는 가정하에 설계된 메서드이기 때문에, 부모 클래스의 구현을 호출하지 않음
  • 성능 최적화:

    • 이 메서드는 필요할 때만 호출되므로, 뷰 로드 작업을 효율적으로 설계해야 함



viewLoad() vs viewDidLoad()

viewDidLoad()에서 UI를 구성하는 것도 가능하다. 사실, 스토리보드 없이 코드로 UI를 작성할 때는 viewDidLoad()에서 주로 작업하는 경우가 많다. UIKit에서는 이미 기본적으로 UIViewControllerview가 생성되기 때문에, viewDidLoad()에서 그 뷰에 UI 요소를 추가하거나 설정하는 방식으로 충분히 구현할 수 있다.


loadView()viewDidLoad()의 차이점

특징loadView()viewDidLoad()
뷰 생성 여부view를 직접 생성해야 함이미 생성된 기본 view를 사용
사용 시점view가 처음 요청될 때 호출loadView() 이후 view가 메모리에 로드된 후 호출
일반적인 사용 사례코드로 루트 뷰를 완전히 커스터마이즈할 때 사용기본 viewUI 구성 및 초기화를 추가할 때 사용
기본 구현 호출 여부super.loadView()를 호출하지 않음super.viewDidLoad()를 항상 호출해야 함

viewDidLoad에서 UI 구성하기

예제 코드:

MainViewController에서 viewDidLoad()를 활용해 UI를 구성하는 방법은 다음과 같습니다:

import UIKit

class MainViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // 기본 뷰 설정
        view.backgroundColor = .white

        // 라벨 생성
        let label = UILabel()
        label.text = "Hello, World!"
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 24)
        label.translatesAutoresizingMaskIntoConstraints = false

        // 버튼 생성
        let button = UIButton(type: .system)
        button.setTitle("Tap Me", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false

        // 뷰에 추가
        view.addSubview(label)
        view.addSubview(button)

        // 오토레이아웃 설정
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 20),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }
}

loadView와의 비교

1. loadView에서 작업

  • view를 생성해야 하므로 반드시 self.view = customView를 명시해야 함
  • view 자체를 완전히 대체할 때 적합함

2. viewDidLoad에서 작업

  • 이미 생성된 viewUI 요소를 추가하거나 레이아웃 설정만 하면 됨
  • 대부분의 경우 더 간단하고 직관적

언제 loadView를 선택해야 할까?

  • 뷰를 완전히 대체하거나, UIKit이 기본적으로 제공하는 view를 사용하고 싶지 않을 때
    • 예: 특정 커스텀 뷰 클래스를 사용해야 하는 경우

예: loadView에서 커스텀 뷰 생성

override func loadView() {
    let customView = MyCustomView() // Custom UIView
    self.view = customView
}

권장 사용

  • 단순한 UI 구성: viewDidLoad() 사용
  • 루트 뷰를 완전히 커스터마이즈: loadView() 사용

결론

viewDidLoad()에서 UI를 구성하는 것은 완전히 유효하며, 간단한 UI 작업에서는 더 권장된다. 하지만 뷰 계층 구조를 완전히 새로 정의해야 하는 경우 loadView()를 사용하는 것이 적합하다.



didReceiveMemoryWarning(), Why not use?

didReceiveMemoryWarning()iOS 13 이상에서 잘 사용되지 않는 이유는 Apple의 메모리 관리 방식이 바뀌면서, 해당 메서드의 호출 빈도가 줄어들거나 실질적인 필요성이 감소했기 때문이다.


1. didReceiveMemoryWarning()의 원래 목적

  • iOS에서 메모리가 부족할 때, 앱이 메모리 해제 작업을 수행할 수 있도록 호출되었음
  • 이 메서드는 주로 캐시 정리불필요한 리소스 해제에 사용되었음

2. iOS 13 이상에서 didReceiveMemoryWarning()의 사용 감소 이유

(1) 메모리 관리의 개선

iOS 13 이후, 시스템이 더 정교한 메모리 관리 메커니즘을 도입했다:

  • 시스템은 앱 전체를 메모리에서 제거하기 전에 앱의 사용되지 않는 리소스를 자동으로 정리
  • 백그라운드로 전환된 앱은 더 빨리 종료되며, didReceiveMemoryWarning() 호출이 발생하지 않는 경우가 많아짐

(2) ARKit 및 SceneKit의 메모리 관리 변화

  • ARKit 및 SceneKit과 같은 리소스 집약적인 API에서 메모리 부족 상황을 더 세밀하게 처리하기 위한 대체 메서드가 도입되었음
  • 이로 인해, didReceiveMemoryWarning()를 사용하는 대신 개발자는 AR 리소스를 정리하거나 재로드하는 전용 API를 사용하게 되었음

(3) Swift 기반 메모리 관리 방식의 변화

  • Swift의 ARC(Automatic Reference Counting)는 더 효율적인 메모리 관리를 제공함
  • 개발자는 메모리 관리 작업을 명시적으로 수행할 필요가 줄어들었으며, 자동 해제된 리소스 덕분에 didReceiveMemoryWarning()의 필요성이 감소했음

(4) Apple의 최신 문서에서의 언급 감소

  • Apple의 최신 문서에서는 didReceiveMemoryWarning()의 사용을 강조하지 않고, 대신 효율적인 메모리 관리 방법(예: 적절한 캐싱 정책, 리소스 사용 패턴)을 권장함

(5) 실제 호출 빈도 감소

  • iOS 13 이상에서, 테스트와 실사용 환경에서 didReceiveMemoryWarning()가 호출되는 사례가 급격히 줄어들었다는 개발자들의 보고가 있음
  • 이는 시스템이 더 공격적으로 앱을 종료하거나, 미리 메모리를 정리하여 경고를 보내지 않기 때문임

3. Apple의 권장 대안

Apple은 didReceiveMemoryWarning() 대신, 다음과 같은 방식을 통해 메모리 사용을 관리하도록 권장함

(1) 캐싱 정책 개선

  • 메모리 사용량이 큰 리소스는 NSCache를 사용해 관리함
  • NSCache는 시스템 메모리 부족 시 자동으로 캐시를 제거함

(2) 리소스 관리

  • 대규모 객체(이미지, 데이터 등)는 더 이상 사용되지 않을 때 수동으로 해제함
  • 뷰 컨트롤러의 viewDidDisappear(_:)에서 불필요한 리소스를 해제함

(3) ARKit 및 SceneKit 리소스 관리

  • AR 리소스 또는 SceneKit 객체는 별도의 관리 메서드를 사용함

4. 문서 및 참고

Apple의 최신 문서에서는 didReceiveMemoryWarning()가 더 이상 강조되지 않으며, 대신 아래와 같은 방식을 권장한다:

  • 적절한 메모리 관리: 불필요한 리소스를 해제하고, 캐시를 효율적으로 관리
  • 백그라운드 전환 처리: 메모리 해제는 앱이 백그라운드로 전환될 때 처리

Apple 개발자 포럼 및 여러 커뮤니티에서도 iOS 13 이상에서 didReceiveMemoryWarning() 호출 빈도가 현저히 줄어든 사례가 많이 언급된다.

profile
Reciprocity lies in knowing enough

0개의 댓글