iOS/SwiftUI

왜 UI 처리는 MainThread 에서 처리하는가?

태애니 2025. 8. 20. 11:13
728x90

 

 
나는 이 질문에 대해서 딱히 생각해본적이 없었다..!
 
마치 그냥 밥 먹으면 이닦아야징 하는 것처럼 + 엄마가 자꾸 밥 먹으면 이닦으라 하듯이 알아서 XCode 에서 알려주니까(으휴ㅠ 으휴!)
 
 
공부하는 내용을 공유하는 시간이 있었는데, 이때 받은 질문 덕분에 어? 그러네.. 왜 밥 먹고 이 닦는 거지? 라는 생각이 그제야 든 것이다.
 
나는 생각해보다가
GUI 처리 - 렌더링이 필요하다 + 애니메이션도 있지... + GPU를 처리해야한다 이런 생각에
UI 처리 자체가 너무 무거워서..? 라고 생각했다.
 
 

아닌데요!!

 
완전 정반대였다.
 
UI 처리라는 것 자체는 무거운 처리가 아니다.
내가 생각하는 렌더링, 애니메이션 처리는 다른 곳에서 하고 있다.
Render Server라는 별도의 프로세스에서 처리된다는 것..
 
 
좀 더 정리를 해보자면..

 

화면에 어떤 UI가 진행될 때 이런 순서로 진행된다.

1. Main Thread: 작업 준비

메인 스레드는 UI 이벤트 처리, 레이아웃 계산, 그리고 레이어 트리(Layer Tree)를 업데이트함.

  1. Layer Tree 업데이트: UIView의 frame, bounds, backgroundColor 같은 속성을 변경하면, 이 변경 사항들이 해당 뷰의 백킹 레이어인 CALayer에 반영됨.
  2. Commit Transaction: 개발자가 애니메이션을 설정하거나 레이어의 속성을 변경하면, Core Animation은 이러한 변경 사항들을 트랜잭션으로 묶어준다. 이 트랜잭션이 렌더 서버로 전송되고, 이 과정을 커밋(commit) 이라고 한다. 보통 메인 스레드의 RunLoop 주기에 맞춰 자동으로 발생한다.

2. Render Server: 렌더링 지시 

Render Server는 iOS 시스템의 중요한 부분으로, 앱과는 완전히 분리된 별도의 프로세스.

  1. 트랜잭션 수신: 렌더 서버는 앱의 메인 스레드로부터 커밋된 레이어 트리를 수신한다.
  2. GPU 명령 변환: 렌더 서버는 수신한 레이어 데이터를 바탕으로 GPU가 이해할 수 있는 GPU 명령어(GPU Commands)를 생성해준다. 예를 들어, backgroundColor 속성은 GPU의 clear 명령으로, transform은 matrix multiplication 명령으로 변환된다.
  3. GPU 전달: 이렇게 생성된 명령어는 Metal(또는 OpenGL ES)과 같은 그래픽 API를 통해 GPU로 전달.

3. GPU: 최종 렌더링

GPU는 렌더 서버가 넘겨준 명령어를 받아서 실제 렌더링 작업을 수행해준다.

  1. 레이어 렌더링: GPU는 각 CALayer를 개별적으로 렌더링해준다. bounds, backgroundColor, contents 같은 속성이 여기서 처리된다.
  2. 합성(Compositing): 렌더링된 각 레이어는 GPU의 하드웨어 합성기(hardware compositor)를 이용해 최종 이미지로 합쳐진다. 이 과정에서 transform, opacity, shadow 같은 속성이 적용된다.
  3. 화면 표시: 최종적으로 합성된 이미지는 화면의 프레임 버퍼에 쓰여지고, 디스플레이의 수직 동기화(V-Sync) 주기에 맞춰 화면에 보여준다.

 
 
 
결론적으로!!! UI 라는 것은 이런 것을 말한다
- UILabel에 텍스트 바꾸기 → 그냥 속성 값 세팅 정도
- 버튼 색상 바꾸기 → 속성 플래그만 변경
- “다음 프레임에 이 뷰 다시 그려야겠다~” 표시만 해두는 수준이 바로 UI 처리이고,
 
내가 생각 했던 실제 무거운 일(렌더링, 픽셀 그리기, GPU 처리)은 Core Animation이나 RenderThread가 뒤에서 하고 있었다.

 

그러고보니 MainThread 라는 말도 자주 쓰면서 정확한 정의를 몰랐다.
 

MainThread(메인 스레드)란?

MainThread는 앱에서 UI 이벤트와 화면 업데이트를 처리하는 주 스레드.
iOS, macOS, Android 등 대부분의 UI 프레임워크는 UI 관련 작업은 메인 스레드에서만 안전하게 수행할 수 있도록 설계되어 있다.
 
그러니까!! 제일 중요하고, 제일 무겁고 중요한 처리를 하는 곳이 아니라
애플리케이션에서 UI 이벤트 처리와 화면 업데이트를 담당 하고 있던 것.
 

  • 역할
    1. 사용자 입력 이벤트 처리 (터치, 제스처, 키 입력 등)
    2. UI 요소(View, Label, Button 등) 속성 변경과 화면 갱신
    3. RunLoop를 통해 이벤트, 타이머, UI 렌더링 스케줄 관리
  • 특징
    • UI 작업은 MainThread에서만 안전하게 수행 가능 (UIKit / SwiftUI / Android View 모두)
    • MainThread에서 무거운 작업을 실행하면 앱 UI가 멈추거나 버벅임 발생
    • 백그라운드 연산은 별도의 스레드에서 처리하고, 결과만 MainThread로 전달


엄마가...이 닦으라고...늘 알려줬었지요....

DispatchQueue.main.async {
    self.label.text = "바ㅏㅏ꾸자"
}
@MainActor
func updateUI() {
    label.text = "변경될 값"
}

Task {
    await updateUI()
}

 
 
 
그럼 RunLoop 는 뭐지?
- RunLoop는 스레드 내부에서 이벤트를 처리하고 스레드의 생명주기를 유지하는 구조체
- MainThread는 앱 시작 시 자동으로 생성되는 RunLoop를 갖고 있는 thread.

  1. Input Source 처리 (터치, 키 입력, 네트워크 이벤트 등)
  2. Timer 처리
  3. Deferred work 처리 (performSelector, DispatchQueue.main.async 작업)
  4. UI 렌더링 요청 처리 (setNeedsDisplay, layout 업데이트)

 
RunLoop는

  • Modes: NSDefaultRunLoopMode, UITrackingRunLoopMode, NSRunLoopCommonModes
  • Mode에 따라 이벤트 큐가 다르게 처리되어, 스크롤 중 UI 업데이트, 타이머 등 충돌 방지한다.
  • RunLoop가 없으면 MainThread는 이벤트를 처리할 수 없고, 앱이 멈추게 되는 것이다.

 
RunLoop 관찰하려면??

  • 개발자용: Instruments → Main Thread Activity 확인
  • UI가 버벅이면 MainThread가 블로킹 상태인지 확인 가능

 

 
 
 
 
 

728x90

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

Swift 에서 observable, event와 state  (1) 2025.04.04