본문 바로가기
iOS/Swift

한장으로 보는 UIViewController & UIView의 LifeCycle

by 빡구동동 2023. 4. 27.

UIViewController & UIView의 LifeCycle(LayoutCycle)을 알아보자.


앱이든 웹이든 앞단을 개발한다면 사용자에게 쾌적한 경험을 제공해야 한다.

그러기 위해서 알아야 할 Life Cycle!!!!


어널언 UIKit의 VC, UIView의 Life Cycle에 대해 알아보자.
하루아침에 공부해서 정리할 양은 아닌 것 같아 꾸준히 업데이트 예정

💡잘못된 정보가 있으면 알려주세요!

 


made by mechanicdong

 

Constraint Update

  • View의 제약 조건을 업데이트
  • Constraint는 실제로 View를 배치하는데는 영향을 주지 않음
  • 특정 조건에 따라 Constraint를 갱신해서 다이나믹한 View를 구성할 수 있음
  • Constraint의 업데이트 순서는 View 계층 구조에서 가장 하위 View -> 상위 View로 올라감

Layout Update

  • 앞서 업데이트된 Constraint 값을 이용해 View가 위치해야 할 값을 갱신
  • 여기서 Layout은 구체적인 View의 Frame 수치 값을 의미
  • Layout의 업데이트 순서는 View 계층 구조에서 가장 상위 View -> 하위 View로 내려감

Render

  • 앞서 구한 Frame 수치 값을 이용해 화면에 그리는 단계
  • UIView의 Draw(rect:)가 담당

View를 다시 Rendering 해야할 때 (23.05.07 추가)

  1. Constraint(제약 조건) 갱신 시
    1. setNeedsUpdateConstraints() 호출로 View의 제약 조건을 업데이트해야 한다는 플래그를 남김
    2. OS는 플래그를 보고 다음 RunLoop Cycle 시 ViewController의 제약 조건을 업데이트하는 프로세스를 실행
  2. Layout 갱신 시
    1. setNeedsLayout() 함수 호출로 View의 레이아웃을 업데이트해야 한다는 플래그를 남김
    2. OS는 플래그를 보고 다음 RunLoop Cycle 시 ViewController의 레이아웃을 업데이트하는 프로세스를 실행

단, OS는 setNeedsLayout() 혹은 setNeedsUpdateConstraints()를 호출하면 View를 바로 다시 그리진 않음

한 번의 RunLoop 안에서 View의 업데이트가 여러번 발생하면 비효율이기 때문에 RunLoop가 끝나고 다음 Cycle을 실행할 때 View의 플래그를 보고 한 번에 모든 View를 갱신함

 

코드로 살펴보자.

빨간색 버튼은 회색 사각형의 사이즈를 늘리며, 파란색은 원래대로 되돌린다.

각각의 버튼에 추가된 action 함수를 보자.

 

// ViewController.swift 

... 프로퍼티 중략 ...

private var widthConstraint: NSLayoutConstraint!
private var heightConstraint: NSLayoutConstraint!

override func viewDidLoad() {
    ...
    view.addSubview(resizableView)
    ...
}

private func changeLayout() {
    UIView.animate(withDuration: 0.3) {
        self.view.setNeedsLayout() // #2
    }
}

/// 사각형 늘리기
private func changeConstraint() {
    NSLayoutConstraint.deactivate([
        widthConstraint,
        heightConstraint
    ])
    widthConstraint = resizableView.widthAnchor.constraint(equalToConstant: 200)
    heightConstraint = resizableView.heightAnchor.constraint(equalToConstant: 200)
    NSLayoutConstraint.activate([
        widthConstraint,
        heightConstraint
    ])

    self.view.setNeedsUpdateConstraints() // #1

    changeLayout()
}
/// 사각형 줄이기
private func undoConstraint() {
    NSLayoutConstraint.deactivate([
        widthConstraint,
        heightConstraint
    ])
    widthConstraint = resizableView.widthAnchor.constraint(equalToConstant: 100)
    heightConstraint = resizableView.heightAnchor.constraint(equalToConstant: 100)
    NSLayoutConstraint.activate([
        widthConstraint,
        heightConstraint
    ])
    changeLayout()
}

 

크기가 늘어나고 줄어드는 건 정상적으로 작동한다. 에러도 뜨지 않는다.

하지만 위와 같은 setNeedsUpdateConstraints()와 setNeedsLayout()을 적용해서 애니메이션을 구현하면 적용되지 않는다.

왜냐하면 UIView의 애니메이션은 View의 업데이트 시작 시점과 종료 시점 각각의 End Point를 자연스럽게 연결시켜주는 동작이기 때문에 View를 바로 갱신시켜줘야 하기 때문이다.

 

LayoutIfNeeded()

 

위에서 setNeedsUpdateConstraints()와 setNeedsLayout()을 통해 OS가 알 수 있게끔 플래그를 남긴 뒤 다음 RunLoop Cycle에서 View를 갱신시키는 방법을 알아봤다.

하지만 애니메이션이 적용되지는 않았다. 시스템의 호출을 기다린 뒤에야 View의 업데이트가 진행되었기 때문이다.

 

LayoutIfNeeded()를 호출하게 되면 다음 RunLoop가 아닌, 즉시 View를 갱신한다.

OS가 호출을 기다리지 않고 바로 호출을 하는 경우는 Animation이다. 

 

즉, 시스템에게 현재 View와 그 하위 View들의 레이아웃을 즉시 갱신하도록 지시하는 메서드다.

이 메서드가 호출되면 레이아웃 시스템은 현재 View 그리고 그 하위 View들에 대해 새로운 레이아웃을 계산하고 애니메이션 효과와 함께 실행된다.

 

// 앞서 #1은 제거
private func changeLayout() {
    UIView.animate(withDuration: 0.3) {
        self.view.layoutIfNeeded() //resizableView가 view에 포함되어 있으므로
    }
}

 

ViewController의 호출은 다음과 같다.

(순서: 로드 -> 확대 -> 축소)

이렇게 changeLayout 메서드를 수정하게 되면 정상적으로 애니메이션이 적용되는 모습을 볼 수 이따.

 


 

UIImage & UIImageView의 관계

UIImage가 이미지를 Load

그 후 Decoding 과정을 거쳐 UIImageView에서 Rendering을 수행

Decoding 과정에서 <버퍼>라는 개념이 나오는데, 이는 연속적인 메모리 영역으로 이미지를 사용할 땐 <이미지 버퍼>라고 부름

  • <이미지 버퍼>는 이미지의 메모리 내부의 표현을 보유한 버퍼
  • 버퍼의 각 요소는 이미지 단일 픽셀의 색상과 투명도를 포함
  • 결과적으로 메모리에 있는 버퍼의 크기는 포함된 이미지의 크기에 비례

UIImage는 UIImageView의 사이즈보다 큰 이미지를 로드할 때 메모리 사용량을 줄이기위해 Thumbnail 기능을 사용

  • 이로 인해 더 작은 디코딩 된 이미지 버퍼를 갖게 됨

정리하면 UIImage가 이미지를 Load - Thumbnail - Decode - UIImage - Render(UIImageView)

UIView / UIImageView 의 Rendering 차이

UIView

Backing Store라는 메모리 버퍼를 사용

이 버퍼는 CALayer에서 사용하며 View의 컨텐츠를 보관하는데 사용

CALayer는 그래픽 컨텍스트를 가지고 있으며, View의 컨텐츠를 Rendering 하는데 사용 됨

  • Backing Store는 이 그래픽 컨텍스트에 대한 메모리 버퍼
  • 즉, Backing Store는 View의 컨텐츠를 렌더링하고 저장하는 데 사용

UIImageView

UIImage가 로드하고 디코딩 된 이미지(Decoded Image)를 Rendering

일반적으로 UIImageView는 UIImage를 렌더링할 때 Backing Store를 사용하진 않음

대신 이미지를 직접 표시하기 위해 CALayer를 사용

그러나 UIImageView에서 이미지를 렌더링할 때, CALayer가 Backing Store를 사용하는 경우도 있음

  • UIImageView가 애니메이션을 표시하는 경우 CALayer는 Backing Store를 사용하여 애니메이션을 렌더링
  • 또한 UIImageView의 contentMode 속성을 변경하여 이미지 렌저링 방식을 조정할 수 있으며 이 경우 역시 Backing Store를 사용

 


Ref.

공식문서
Varga Zolt
알파카님
beankhan님
김종권님
zeke님

naljin님

https://developer.apple.com/videos/play/wwdc2018/219/

'iOS > Swift' 카테고리의 다른 글

[Swift] Async Await와 MainActor의 관계에 대한 고찰  (1) 2023.04.26
[Swift] 다양한 문자열 처리 방법  (0) 2023.04.05

댓글