컴포즈 런타임(The Compose Runtime)
Compose Runtime 내부 동작 정리: SlotTable → ChangeList → Applier → setContent
Compose를 쓰다 보면 “상태 바뀌면 알아서 UI가 갱신된다” 정도로 이해하고 넘어가기 쉬운데, 내부에서는 꽤 체계적인 파이프라인이 돌아간다. 핵심은 한 문장으로 요약하면 이렇다.
SlotTable에 컴포지션 상태를 저장하고, 변경은 ChangeList에 모아뒀다가, Applier가 실제 노드 트리에 반영한다.
이 글은 그 흐름을 SlotTable/ChangeList/Applier/setContent 관점에서 정리한 내용이다.
1) SlotTable: “현재 컴포지션 상태 저장소”
SlotTable은 Compose Runtime이 Composition의 현재 상태를 들고 있기 위해 사용하는 인메모리 구조다.
초기 composition 때 테이블을 채우고
이후 recomposition 때마다 갱신한다
remember값, 호출 위치 기반 그룹 정보, 매개변수 관련 정보 등이 여기 쌓인다
즉, 런타임이 “지금 UI가 어떤 구조/상태인지”를 판단하는 기준이 SlotTable이다.
2) 내부 구조: 갭 버퍼 기반 + (groups, slots) 2개 배열
SlotTable은 “빠르게 읽고/수정”하기 위해 갭 버퍼(gap buffer) 아이디어를 활용한다. 구현 관점에서 크게 두 덩어리로 나뉜다.
groups: 그룹(Composable 단위) 메타데이터를 저장하는 영역slots: 그 그룹에 속하는 실제 데이터(예: remember 값)를 저장하는 영역
그리고 갭(gap) 이라는 “비어 있는 연속 구간”을 두고, 삽입/삭제/교체가 일어나면 갭을 이동시키면서 덮어쓰는 방식으로 비용을 줄인다.
예를 들어 조건 분기 UI가 있다면, 조건이 바뀔 때마다 트리를 다 갈아엎는 대신 기존 위치로 갭이 이동한 뒤 필요한 부분만 덮어쓰기에 가까운 방식이 가능해진다.
3) NonRestartableComposable과 “그룹 타입” 감각 잡기
예를 들어 아래처럼 단순한 조건 분기가 있다고 하자(개념 예시):
@NonRestartableComposable
fun ConditionalText() {
if (a) Text("a") else Text("b")
}
여기서 포인트는 @NonRestartableComposable 같은 힌트 때문에 런타임이 재시작 가능한 그룹(restartable) 대신, 상황에 따라 교체 가능한 그룹(replaceable) 형태로 잡아버릴 수 있다는 것.
정확한 내부 동작은 케이스마다 다르지만, 스터디 관점에서는 이렇게 이해하면 편하다.
“재시작 가능”은 부분 recomposition의 범위를 만들 여지가 큼
“교체 가능”은 그 위치를 통째로 갈아끼우는 모델에 더 가깝다
4) SlotReader / SlotWriter: 읽기는 다중, 쓰기는 단일
SlotTable을 접근하는 역할도 분리된다.
SlotReader: 여러 개가 동시에 존재 가능(읽기 전용 탐색)
SlotWriter: 단 하나만 존재 가능(테이블 수정)
Writer가 단일인 이유는 간단하다. 동시에 여러 Writer가 들어오면, 갭 이동/삽입/삭제 같은 작업에서 데이터 정합성이 깨질 위험이 커지기 때문.
그래서 코드 레벨에서도 “Writer 열기 전에 Reader가 모두 닫혀 있어야 한다” 같은 제약이 강하게 걸려 있다. (SlotTable 내부 주석에 이런 내용이 꽤 직접적으로 명시됨)
5) ChangeList: “지연된 변경사항 큐(명령 리스트)”
SlotTable이 “상태 저장소”라면, **ChangeList는 ‘할 일 목록’**에 가깝다.
Composable이 실행되면서 “노드를 만들자/옮기자/삭제하자/속성을 바꾸자” 같은 작업이 발생하는데, 이걸 즉시 트리에 반영하지 않고 명령(operations) 형태로 차곡차곡 쌓아둔다.
이게 ChangeList의 본질이다.
Compose 변경사항을 순차 저장
컴포지션이 끝나는 타이밍에 일괄 실행(flush)
그래서 ChangeList는 “명령 큐(Command Queue)” 같은 표현이 오히려 직관적이다.
6) Layout이 노드를 “즉시 생성”하는 게 아닌 이유
Compose UI의 기본 빌딩블록 중 하나인 Layout을 보면 내부에서 ReusableComposeNode 같은 걸 호출한다.
이걸 보고 “아 Layout이 LayoutNode를 바로 만들고 붙이는구나” 라고 오해하기 쉬운데, 실제로는 이렇게 보는 편이 정확하다.
Layout은 Composer에게 ‘이 위치에서 노드를 생성/사용하고 업데이트하라’는 규칙을 알려주는 역할이고,
실제로 트리 반영은 ChangeList → Applier 단계에서 확정된다.
ReusableComposeNode 내부 흐름도 그 감각을 잘 보여준다.
삽입 중이면 create
아니면 use(재사용)
update 블록으로 속성 반영
자식 content는 replaceable group으로 감싼 뒤 호출
마지막에 endNode로 닫기
즉, “지금 당장 트리에 붙이는 코드”라기보다, 런타임이 트리를 만들 수 있도록 절차를 예약/기록하는 쪽에 가깝다.
7) Applier: 실제 노드 트리에 반영하는 담당자
ChangeList에 쌓인 작업은 결국 누가 실행할까? → Applier다.
Applier는 인터페이스 자체가 매우 명확하다.
down / up 으로 트리 탐색
insert / remove / move 같은 구조 변경
apply로 current 노드에 속성 적용
Compose UI(Android)에서는 보통 UiApplier가 쓰이고, 노드 타입은 LayoutNode다.
그리고 Android 쪽 구현에서는 bottom-up 삽입 전략을 선호한다.
이유는 요약하면 다음처럼 이해할 수 있다.
top-down은 “부모에 넣는 순간 부모/상위가 계속 알림받아야” 해서 비용이 커질 수 있음
bottom-up은 “자식 조립 후 부모에 한번에 붙이기”가 가능해, 중복 알림을 줄이기 좋음
그래서 UiApplier는 insertTopDown은 사실상 무시하고, insertBottomUp에서 current.insertAt(...) 같은 식으로 위임하는 구조를 취한다.
8) setContent: “컴포지션 생성”의 진입점
마지막으로 Android에서 컴포지션이 어떻게 시작되냐는 질문은 결국 setContent로 귀결된다.
AndroidComposeView를 준비(없으면 생성)Composition(UiApplier(root), parentContext)를 만들어 래핑이후 WrappedComposition이 각종 안드로이드 환경(LocalContext, LocalConfiguration 등)을 CompositionLocal로 공급할 수 있게 세팅
그래서 우리가 아무렇지 않게 쓰는 LocalContext, LocalConfiguration, LocalLifecycleOwner 같은 값이 “그냥 magically 생기는 것”이 아니라, 초기 Composition을 만들 때 플랫폼 쪽에서 Provider를 깔아주는 구조라는 걸 이해할 수 있다.
9) Snapshot과 MutableState 내부 감각
초기 컴포지션/재구성에서 빠지지 않고 나오는 게 Snapshot 시스템인데, 스터디 관점에서는 아래 정도 감각만 잡아도 도움이 된다.
State 값의 읽기/쓰기는 “현재 스냅샷”을 기준으로 동작
변경은 스냅샷 컨텍스트 안에서 안전하게 일어나고
마지막에 apply로 원자적으로 반영되는 모델
mutableStateOf 내부를 보면 StateRecord를 통해 “스냅샷별 값”을 유지하는 식으로 설계되어 있고, 레코드가 next로 연결되면서 “현재 스냅샷에서 읽어야 할 record”를 찾아가는 형태가 보인다.
(여기까지 들어가면 runtime 스터디가 갑자기 snapshot 자료구조 스터디로 확장되니, 일단은 “스냅샷 단위로 상태를 안전하게 관리한다” 정도만 가져가도 충분하다고 봄)
최종 흐름 한 줄 정리
Composable 실행 → SlotTable 기반으로 현재 상태를 읽고
변경은 ChangeList에 “명령” 형태로 누적하고
컴포지션이 끝나면 Applier(UiApplier)가 그 명령을 실행해서
실제 LayoutNode 트리가 갱신된다
Android에선 setContent가 이 전체 시스템을 시작시키는 관문이다
댓글
댓글 쓰기