iOS

[iOS] Main Event Loop & UI Update Cycle

guswlsdk 2025. 3. 11. 14:57
반응형

Main Event Loop

  • 메인 스레드의 Run Loop이다. Main Run Loop로 불리기도 한다.
  • 앱이 시작될 때 생성된 UIApplication에 의해 자동으로 생성되고 실행된다. run() 메소드 동작 과정 중에 main loop를 실행한다.
  • Input Source와 Timer Soruce 두 종류의 이벤트를 처리한다.
  • Input Sorce를 통해 사용자의 저수준 이벤트를 수신 받는다.

 

Main Event Loop의 동작 과정 

 

https://jeonyeohun.tistory.com/336

 

1. Input Source로부터 입력 받은 저수준의 이벤트를 도착한 순서대로(FIFO) 이벤트 큐(Event Queue)에 넣는다. 

2. Application object는 이벤트 큐에 있는 최상위 이벤트 객체를 가져와 해석하고 그에 상응되는 UIEvent(iOS)로 변환한다.

3. 다음 Core Object의 핸들러를 호출하고 이러한 핸들러는 개발자가 작성한 코드를 호출한다.

4. 이러한 루틴이 끝났을 때 함수들이 모두 반환되면 Main Run Loop로 돌아가 다시 Update Cycle이 시작된다.

 

다음 과정들은 앱이 종료될 때까지 반복한다.

 

 

Update Cycle

Update Cycle은 Application이 유저로부터 모든 이벤트 핸들링 코드를 수행하고 다시 Main Run Loop로 컨트롤을 반환하는 지점이다.

이 지점에서 Update Cycle은 View를 배치(layout)하고 그리고(display), 제약(contstraint)을 설정하는 역할을 한다. 

 

만약 이벤트 핸들러를 처리하는 과정에서 UIView에 변화를 준다면, 이 UIView는 다시 그려져야(redraw)할 것이다. 이는 다음 Update Cycle에서 시스템이 해당 변화를 처리한다. 

 

다음 Update Cycle에서 처리되면 사용자가 불편함을 느끼지 않을까?

 

iOS에서는 초당 60프레임(기기마다 다름)만큼의 굉장히 빠른 속도로 보여주기 때문에 유저는 UI와 상호작용하는 과정에 대해 지연을 느끼지 못 한다. 그러나 실제 동작 과정에서는 이벤트가 처리되는 시점과 실제로 View가 다시 그려지는 시점엔 차이가 있기 때문에 UIView는 우리가 원하는 시점에 업데이트 되지 않을 수 있다.

 

이 때 UIView의 마지막 Layout이나 Content에 대해 계산을 해야 하는 시점이라면, 예전 정보를 가지고 UIView를 조작할 수 있는 가능성이 있다는 위험이 있다. 위와 같은 문제들을 쉽게 피하고 원인을 분석하기 위해서 UIView는 Update Cycle을 제어할 수 있는 메서드를 제공한다.

 

 

1. Layout

Layout은 화면에서 UIView의 크기(Size)와 위치(Position)를 나타낸다. 모든 View에는 SuperView의 좌표계에서 SubView가 존재하는 위치와 크기를 정의하는 Frame을 가지기 때문에 UIView는 View의 크기나 위치의 변동 사항을 시스템에게 알려주고 layout을 업데이트할 수 있는 메소드를 제공한다.

 

1-1. layoutSubviews()

이 메소드를 호출한 뷰와 그 하위에 있는 모든 subview의 크기를 다시 계산하거나 위치를 다시 지정하도록 한다. Frame의 변화에 따라 업데이트할 필요가 있을 경우 시스템이 layoutSubViews()를 호출한다. 구체적으로 정의하고 싶다면 override할 수 있다. view와 모든 subview의 layoutSubviews()를 재귀적으로 호출하기 때문에 부하가 큰 메서드이다. 때문에 직접 호출하면 안 된다

 

layoutSubViews() 직접 호출하지 않아도 호출되는 경우

- 뷰의 크기가 바뀌었을 때
- subview가 추가 되었을 때
- 스크롤 뷰에서 스크롤이 발생했을 때
- 디바이스 회전했을 때
- 뷰의 Constraint를 변경했을 때
해당 이벤트의 경우 자동으로 다음 Update Cycle에서 layoutSubViews()를 호출한다.

 

view의 layoutSubviews()가 완료되면 해당 뷰를 소유하고 있는 ViewController에서 viewDidLayoutSubview()가 호출된다. layoutSubViews()가 뷰의 레이아웃이 업데이트된 시점을 신뢰할 수 있는 유일한 메서드이기 때문에 레이아웃 및 크기를 의존하는 동작은 viewDidLayoutSubviews에 넣어야 한다.

 

1-2. setNeedsLayout()

layout에서 직접 호출할 수 있는 메서드 중 가장 비용이 적은 메서드이다. 시스템에게 view의 layout을 다시 계산해야 한다고 알려준다. 다음 Update Cycle에 update 되도록 마킹한다.

 

이후 시스템이 해당 뷰에 대한 layoutSubviews()를 호출하고 모든 subview의 layoutSubViews()를 호출한다.

 

 

1-3. layoutIfNeeded()

layout에서 직접 호출할 수 있는 메서드이다. 다음 Update Cycle이 아닌 즉시 layoutSubViews()를 호출한다. 이 때 만약 view가 재조정 되어야할 이유가 없다면 호출되지 않는다. 동일한 Run Loop에서 layout의 변동 사항이 없는데 2번 호출된다면 두 번째 호출은 무시된다.

 

즉시 호출해야 하는 이유가 아니라면 setNeedsLayout()을 사용하여 Run Loop 하나에 view 업데이트 한 번만 이루어지게 하는 것이 이상적이라고 한다.

 

layoutIfNeeded()는 애니메이션을 사용하는 상황에서는 유용할 수 있다고 한다. 애니메이션이 시작하기 전에 호출해서 모든 레이아웃 업데이트가 애니메이션 전에 수행될 수 있도록 하기 때문이다.

// layout 변경
UIView.animate(withDuration: 3){
	self.view.layoutIfNeeded()
}

 

2. Display

layout이 뷰의 위치와 크기를 의미한다면 display는 이를 제외한 뷰의 레이아웃이나 subview와 관련 없는 속성들을 포함한다.

ex) Color, Text, Image, CG(Core Graphics)...

 

2-1. draw(_:)

호출한 view의 디스플레이를 그리는 메소드이다.

layout에서의 layoutSubviews()와 같은 역할을 한다. 그러나 draw는 subview들의 draw까지 호출하진 않는다. 마찬가지로 직접 호출하여 사용하는 것은 좋지 않다.

 

자동으로 호출하는 트리거는 view의 bound를 변경하는 것이 있다.

 

2-2. setNeedsDisplay()

view에 Content가 업데이트 되게 하는 내부 플래그를 활성화 시키고 다음 Update Cycle에서 이 플래그가 활성화 되어 있는 view들의 draw()를 호출하여 다시 그린다. 대부분의 UI 컴포넌트를 업데이트 하는 것은 플래그를 활성화 시켜서 개발자가 굳이 setNeedsDisplay()를 하지 않아도 뷰가 다시 그려지도록 유도한다.

 

layout의 viewifNeeded처럼 즉시 Update Cycle이 처리하도록 하는 방법이 없다. 만약 즉각적인 업데이트가 필요하다면 아래의 코드처럼 didSet 같은 프로퍼티 옵저버로 draw 메서드를 오버라이딩 할 수 있다. 먄약 View의 일부분만 다시 그리길 원한다면 인자로 rect을 전달 가능하다.

class MyView: UIView {
    var numberOfPoints = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        switch numberOfPoints {
        case 0:
            return
        case 1:
            drawPoint(rect)
        case 2:
            drawLine(rect)
        case 3:
            drawTriangle(rect)
        case 4:
            drawRectangle(rect)
        case 5:
            drawPentagon(rect)
        default:
            drawEllipse(rect)
        }
    }
}

 

 

3. Contstraints

Constraint는 뷰들이 가지는 제약 조건을 의미한다.

Constraints 관련 메서드는 AutoLayout을 적용한 뷰에만 적용된다. AutoLayout에는 View를 layout하고 redraw하는 과정에 3단계가 있다.

 

  1. Constraint Update 단계 
    1. 시스템은 View에 필요한 모든 제약조건을 계산하고 설정한다.
  2. Layout 단계
    1. Layout Engine이 View와 SubView의 frame을 계산하고 Layout을 배치한다.
  3. Display 단계
    1. 필요에 따라 draw()를 호출하여 필요한 경우 View의 내용을 그린다.

 

3-1. UpdateContraints()

AutoLayout을 사용하는 view가 동적으로 변화하는 Constraints를 적용할 수 있게끔 해주는 메소드로 직접 호출은 금지된다.

사용할 땐 override해서 동적으로 변화하는 constraints을 구현하는 것이 바람직하고 정적인 constraints는 Interface Builder(storybaord, xib)로 잡거나 view의 init 또는 viewDidLoad에 구현하는 것이 바람직하다.

 

자동으로 호출될 수 있도록 하는 트리거는 다음과 같다.

 

  • view 계층적 구조에서 view 삭제
  • constraints 활성화/비활성화
  • constraints 우선순위(priority)나 상수값 변경

 

3-1. setNeedsUpdateConstraints()

다음 Update Cycle에서 Constraint가 업데이트 되는 것을 보장한다.

setNeedLayout(), setNeedDisplay()와 유사하게 동작한다.

 

 

3-2. updateConstraintsIfNeeded()

layoutIfNeeded()와 유사하다. 그러나 오직 AutoLayout을 사용하는 뷰에만 유효하다. Update Flag를 검사하여 업데이트가 필요하다고 판단될 시에 updateContraints()를 호출한다. 시스템에 많은 부하가 생긴다.

 

 

3-3. invalidateIntrinsicContentSize() 

AutoLayour을 쓰는 뷰들 중에 일부는 intrinsicContentSize 프로퍼티를 가진다. intrinsicContentSize란 view의 content가 이상적으로 나타나려면 필요한 공간을 의미한다. (ex. UILabel의 text에 따라 가로 세로가 결정되는....)

 

커스텀 뷰에 intrinsicContentSize를 적용하거나 오버라이딩 되어 변경된 intrinsicContentSize를 적용하고 싶을 때 사용한다.

intrinsicContentSize()를 호출하는 것은 View가 가지고 있는 intrinsicContentSize가 오래돼 다음 Update Cycle에 다시 계산되어야 한다는 플래그를 활성화한다.

 

 

 

 

 

 

 

 

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

 

Run Loops

Run Loops Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread bus

developer.apple.com

https://jeonyeohun.tistory.com/336

 

[iOS] iOS는 화면을 어떻게 그릴까? RunLoop 부터 Constraint, Layout, Display 까지

런루프부터 화면에 뷰가 그려지기까지. 스터디를 하면서 기술발표가 있었는데요, 준비한 내용을 정리해두겠습니다! 거의 피피티에 모든걸 다 담아서 글은 많이 없을 것 같아요ㅎㅎ RunLoop(런루

jeonyeohun.tistory.com

https://tech.gc.com/demystifying-ios-layout/

 

Demystifying iOS Layout

Some of the most difficult issues to avoid or debug when you first start building iOS applications are those dealing with view layout and content. Often, these issues happen because of misconceptions about when view updates actually occur. Understanding ho

tech.gc.com

 

반응형