[WWDC] High Performance Auto Layout

dev-yong·2021년 3월 12일
0

Internals and intuition

The Render Loop

TL;DR

  • 초당 120번 실행된다.
  • Update Cosntraints, Layout, Display 3가지의 단계로 구성되어 있다.
  • 불필요한 작업을 피할 때 매우 유용하다.
  • Render Loop는 잠재적으로 초당 120번 실행되는 프로세스이다.

  • Render Loop는 모든 콘텐츠가 각 프레임 에 대해 준비되었는지 확인한다.

  • 이는 Update Constraints, Layout, Display 3가지의 단계로 구성되어져 있다.

    1. 모든 view들이 Leaf 에서 부터 window까지 View Hierarchy를 따라 올라가며 updateConstraints 를 수신한다.

    2. 모든 view들이 Window 부터 Leaf까지 내려가며 layoutSubviews() 를 수신한다.

    3. 마지막으로, 필요한 경우 모든 view가 그려진다.

  • 각 단계는 동일한 목적을 갖는 평행한 method 세트를 지니고 있다.
    • 이것들의 목적은 낭비되는 작업을 피하며 작업을 연기하고 완전히 건너뛰게하는 것이다.
    • 예를 들어, UILabel의 크기를 구성하는 데에 있어 많은 속성들이 존재한다.
      • 속성이 변경될 때마다 텍스트 사이즈를 재측정하는 방법이 존재한다.
      • 그러나 일반적으로 이러한 항목을 연속적으로 변경하기 때문에 매우 비효율적이다.
      • 처음 Label을 설정할 때, 많은 property setter를 호출할 것이고, 각각의 setter에서 텍스트 사이즈를 다시 측정한다면 중간 것들이 모두 낭비될 것이기에 마지막에 측정되기만을 원할 것이다.
      • 이것이 Render Loop가 주는 이점이다.
      • Property setter에서 setNeedsUpdateConstraints() 를 호출하면 프레임이 화면으로 이동하기 직전에 updateConstraints() 를 호출하게 할 수 있다.

Example

var myConstraints: [NSLayoutConstraints] = []

override func updateConstraints() {
  	// 1. 모든 constraints에 대하여 비활성화한다.
    NSLayoutConstraint.deactivate(myConstraints) 
    myConstraints.removeAll()
  	// 2. Layout을 구현하는 constraints를 생성한다.
    let views = ["text1":text1, "text2":text2]
    myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-[text1]-[text2]",
                                                    options: [.alignAllFirstBaseline],
                                                    metrics: nil, views: views)
    myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-[text1]-|",
                                                    options: [],
                                                    metrics: nil, views: views)
  	// 3. 모든 constraints를 활성화한다.
    NSLayoutConstraint.activate(myConstraints)
  	
    super.updateConstraints()
}
  • updateConstraints() 에서 모든 constaints를 deactivate / activate 하는 것은
    • layoutSubviews() 에서 모든 subviews를 remove / add 하는 것과 동일하다.
    • 불필요한 작업이 발생하고 있기 때문에, 한 번 이상은 작업을 수행하지 않도록 한다.
var myConstraints: [NSLayoutConstraints] = []

override func updateConstraints() {
    // 불필요하게 모든 constraints를 deactivate/activate 하지않도록 한 번 이상은 작업을 수행하지 않도록한다.
    
    if myConstraints.isEmpty {
        
        NSLayoutConstraint.deactivate(myConstraints)
        myConstraints.removeAll()
        
        let views = ["text1":text1, "text2":text2]
        myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-[text1]-[text2]",
                                                        options: [.alignAllFirstBaseline],
                                                        metrics: nil, views: views)
        myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-[text1]-|",
                                                        options: [],
                                                        metrics: nil, views: views)
        
        NSLayoutConstraint.activate(myConstraints)
    }
    super.updateConstraints()
}

Activating a Constraint

  • Constraints를 추가하고자 하는 view 가 존재한다.
  • View는 Window에 있다.
  • Window는 Engine이라고 하는 internal 객체를 지니고 있다(hanging off).
  • Engine이 Auto Layout의 계산의 핵심이다.

  • Cosntraints를 view에 추가하면, constraints에 해당하는 방정식(equation)을 만들고 해당 방정식을 Engine에 추가한다.
    • 만일 방정식을 "X에 대한 해결"이라고 말한다면, "X는 변수"이다.

  1. 각 방정식들이 Engine에 추가된다.
  2. Engine이 이러한 변수 중 하나에 값을 할당 할 때마다 view에 변수의 출처를 알리고 이것이 변경되었다고 말한다.
  3. Update Cosntraints 단계로 발생되며, setNeedsLayout() 를 받았기 때문에, 어떠한 지점에서 Layout 단계로 이동한다.
  4. UIView는 layoutSubViews()를 받게 된다.
    • 이는, Engine에서 프레임으로 데이터를 복사하는 것이다.
    • View는 Engine에 변수의 값이 무엇인지 물어볼 것이다.
    • Engine은 그 값을 알려주고, setCenter, setBounds 를 호출한다.

Unrelated Views Don't Interact

  • 연관되지 않은 View들에 대하여 Engine은 개별적인 방정식을 추가하게되고, 이는 linear 성능을 보여주게 된다.

Engine은 Layout Cache이고 Dependency Tracker이다.

  • 어떤 constraints가 어떤 View에 영향을 미치는지 이해하고, 변경하면 필요한 것만 업데이트한다.

Building Efficient Layouts

Avoid Constraint Churn

  • 모든 constraints를 지우는 것을 피하라.
    • 필요 없이 frame을 배치하게 되거나, 다시 배치할 필요 없는 view가 전달될 것이다.
  • static constaints는 한번만 추가하라.
    • 잠재적인 layout에 공통적으로 적용되는 Constraints이 있는 경우 하나를 추가 한 다음 그대로 두어라
  • Constraints가 변경이 필요할 때만 변경하여라.
  • View를 remove 보다 hide하도록 하라.

Intrinsic Content Size

  • 모든 view들이 intrinsicContentSize가 필요하지는 않다.
  • Non-View 컨텐츠를 갖는 View (e.g. UIImageView, UILabel) 의 경우, 내용의 크기(e.g. image size, text size)를 반환한다.
  • UIViewintrinsicContentSize 를 이용하여 constraints를 만든다.
  • intrinsicContentSizeengine에 넣을 크기 정보를 전달하는 방법이다.

System Layout Size Fitting Size

  • systemLayoutSizeFitting(_:)engine에서 크기 정보를 다시 가져 오는 방법이다.
  • 생각보다 가벼운 작업이 아니다.

Getting Size from the Engine

  1. systemLayoutSistemFitting(_:) 가 호출되면, Engine이 생성된다.
  2. Engine에 Cosntraints가 추가된다.
  3. Layout이 해결된다.
  4. 상위 view들의 frame 크기가 반환된다.
  5. Engine이 폐기된다.
profile
🧑🏻‍💻 iOS Developer @Kakao

관심 있을 만한 포스트

0개의 댓글