[Compose] Compose Runtime

 

– Composer, SlotTable, ChangeList를 중심으로

Jetpack Compose는 선언형 UI를 효율적으로 관리하기 위해 런타임에서 Composition의 상태 저장UI 변경 적용을 명확히 분리한다.
이를 위해 Compose Runtime은 Composer, SlotTable, ChangeList, 그리고 이를 실제 UI로 반영하는 Applier, Recomposer라는 구성 요소들을 사용한다.

이 글에서는 Compose Runtime 내부에서 Composable이 실행되고, 상태가 저장되며, 변경 사항이 실제 UI로 반영되는 전체 흐름을 중심으로 구조를 정리한다.


전체 흐름 요약

  1. Composable 함수가 실행되며 Composer를 통해 슬롯 테이블(SlotTable) 에 현재 상태를 기록

  2. 이전 Composition과 비교해 필요한 UI 변경 사항을 변경 목록(ChangeList) 으로 모델링

  3. Composition이 완료되면 ChangeList가 Applier에 의해 실제 UI 트리에 적용

  4. Recomposer는 상태 변경을 감지하고 필요한 범위만 재구성(Recomposition)


SlotTable – Composition 상태 저장소

SlotTable은 Composition의 현재 상태를 메모리에 저장하는 트리 구조의 데이터 구조다.
Composable 함수 호출 순서에 따라 그룹(Group) 단위로 정보가 저장되며, Recomposition 시 이전 상태와 비교하는 기준이 된다.

주요 역할

  • Composable 호출 위치와 identity 관리

  • 파라미터, remember 값, CompositionLocal 값 저장

  • Recomposition 시 변경 여부 판단

  • Positional memoization을 통한 불필요한 재실행 방지

내부 구조 특징

  • Group 단위 저장

    • 시작 마커 + 데이터 슬롯 + 종료 마커

    • 조건문, key, restartable 함수 등에 따라 그룹 타입이 달라짐

  • Gap Buffer 방식

    • 중간 삽입/삭제가 잦은 Composable 트리 특성에 최적화

    • 구조 재할당 없이 빠른 수정 가능

SlotTable은 “현재 Composition이 어떤 상태인지”를 표현하는 추상적인 메모리 표현(representation) 이라고 볼 수 있다.


ChangeList – 지연된 UI 변경 목록

ChangeList는 실제 UI 트리에 반영될 변경 작업을 지연(lazy) 방식으로 기록하는 구조다.

Composable 함수가 실행되면서 발생하는 노드 추가, 제거, 이동 등의 작업은 즉시 UI에 적용되지 않고,
Change라는 람다 형태의 작업으로 ChangeList에 누적된다.

ChangeList의 역할

  • UI 트리에 대한 패치(patch) 모델링

  • 변경 사항을 모아서 한 번에 적용하여 성능 최적화

  • SlotTable 업데이트와 UI 변경을 분리

동작 과정

  1. Composer가 SlotTable을 기준으로 변경 필요 여부 판단

  2. 필요한 작업을 Change로 생성하여 ChangeList에 기록

  3. Composition 종료 후 Applier가 ChangeList를 실행

  4. 실제 UI 트리와 SlotTable이 최신 상태로 동기화

즉, SlotTable이 “상태”를 저장한다면,
ChangeList는 “무엇을 바꿀지”를 저장한다.


Composer – Composable과 Runtime의 중재자

Composer는 개발자가 작성한 Composable 코드와 Compose Runtime 사이를 연결하는 핵심 컴포넌트다.
모든 Composable 함수에는 컴파일 시 $composer라는 합성 파라미터가 추가되며, 이를 통해 런타임과 상호작용한다.

주요 책임

  • SlotTable에 상태 읽기/쓰기

  • 그룹 시작/종료 관리

  • 노드 생성 및 재사용 지시

  • ChangeList에 변경 작업 기록

  • SideEffect, CompositionLocal 관리

노드 방출(Emit)

Composable이 실행되면 Composer는 다음과 같은 작업을 수행한다.

  • startNode() / createNode() / endNode() 호출

  • 노드 생성 또는 재사용 여부 결정

  • 노드 속성 변경 사항을 Change로 기록

이 과정을 노드 방출(emit) 이라고 하며,
실제 UI 생성이 아니라 “UI를 만들기 위한 변경 사항을 기록”하는 단계다.


Group과 RecomposeScope

Group

Group은 Composable 함수 호출 본문을 감싸는 최소 단위로, SlotTable에 저장되는 핵심 구조다.

  • Replaceable Group: if/else 같은 조건 분기

  • Movable Group: key() 기반 재정렬

  • Restartable Group: 재구성이 가능한 Composable 함수

RecomposeScope

RecomposeScope는 스마트 재구성(Smart Recomposition) 을 가능하게 하는 단위다.

  • 상태(State)를 읽으면 해당 Scope가 활성화

  • 상태 변경 시 Scope가 invalidate

  • Recomposer는 무효화된 Scope만 다시 실행

  • 변경 없는 Scope는 skip

이를 통해 Compose는 필요한 부분만 선택적으로 재실행한다.


SideEffect & CompositionLocal

SideEffect

Composer는 사이드 이펙트도 기록한다.
모든 SideEffect는 Composition이 성공적으로 끝난 이후 실행된다.

  • SideEffect: 매 Recomposition 후 실행

  • DisposableEffect: Composition 진입/이탈 시

  • LaunchedEffect: Composition 범위 내 코루틴 실행

Composition 단계의 순수성을 보장하기 위해 외부 상태 변경은 apply 단계 이후로 미뤄진다.

CompositionLocal

CompositionLocal의 공급자와 값 역시 SlotTable에 그룹 형태로 저장된다.
CompositionLocal.current 호출은 Composer를 통해 현재 SlotTable 위치에서 값을 조회한다.


Applier – 추상 표현을 실제 UI로

Applier는 SlotTable과 ChangeList에 기록된 추상적인 변경 사항을 실제 UI로 구체화(materialize) 하는 역할을 한다.

역할

  • ChangeList에 기록된 operations 실행

  • UI 노드 삽입, 제거, 이동 수행

  • 플랫폼별 UI 트리에 반영

주요 구현체

  • UiApplier: Android View 트리

  • VectorApplier: VectorPainter 노드 트리

Applier는 Compose Runtime과 실제 UI 사이의 브리지 역할을 담당한다.


Recomposer – Composition 생명주기 관리자

Recomposer는 Composition의 생명주기와 Recomposition 실행을 총괄한다.

주요 기능

  • Snapshot 시스템과 연동하여 상태 변경 감지

  • 무효화된 RecomposeScope 추적

  • 프레임 타이밍에 맞춰 Recomposition 스케줄링

  • CoroutineContext 기반 비동기 관리

State 변경 → invalidation → 선택적 Recomposition이라는 흐름을 통해 효율적인 UI 업데이트를 수행한다.


정리

  • SlotTable: Composition의 현재 상태 저장

  • ChangeList: UI 변경 작업의 지연된 목록

  • Composer: 상태 추적과 변경 모델링의 중심

  • Applier: 추상 변경을 실제 UI로 변환

  • Recomposer: 상태 변경 기반 재구성 조율

Compose Runtime은 이 구조를 통해
선언형 UI의 단순함과 고성능 렌더링을 동시에 달성한다.

댓글

이 블로그의 인기 게시물