12월, 2025의 게시물 표시

컴포즈 런타임(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과 “그룹 타입” 감각 잡...

[Compose] Compose Runtime

  – Composer, SlotTable, ChangeList를 중심으로 Jetpack Compose는 선언형 UI를 효율적으로 관리하기 위해 런타임에서 Composition의 상태 저장 과 UI 변경 적용 을 명확히 분리한다. 이를 위해 Compose Runtime은 Composer , SlotTable , ChangeList , 그리고 이를 실제 UI로 반영하는 Applier , Recomposer 라는 구성 요소들을 사용한다. 이 글에서는 Compose Runtime 내부에서 Composable이 실행되고, 상태가 저장되며, 변경 사항이 실제 UI로 반영되는 전체 흐름 을 중심으로 구조를 정리한다. 전체 흐름 요약 Composable 함수가 실행되며 Composer를 통해 슬롯 테이블(SlotTable) 에 현재 상태를 기록 이전 Composition과 비교해 필요한 UI 변경 사항을 변경 목록(ChangeList) 으로 모델링 Composition이 완료되면 ChangeList가 Applier 에 의해 실제 UI 트리에 적용 Recomposer 는 상태 변경을 감지하고 필요한 범위만 재구성(Recomposition) SlotTable – Composition 상태 저장소 SlotTable은 Composition의 현재 상태를 메모리에 저장하는 트리 구조의 데이터 구조 다. Composable 함수 호출 순서에 따라 그룹(Group) 단위로 정보가 저장되며, Recomposition 시 이전 상태와 비교하는 기준이 된다. 주요 역할 Composable 호출 위치와 identity 관리 파라미터, remember 값, CompositionLocal 값 저장 Recomposition 시 변경 여부 판단 Positional memoization을 통한 불필요한 재실행 방지 내부 구조 특징 Group 단위 저장 시작 마커 + 데이터 슬롯 + 종료 마커 조건문, ...

Compose 컴파일러: 선언형 UI

Compose 컴파일러: 선언형 UI  Compose의 전체 흐름을 한 줄로 요약하면 꽤 단순하다. 개발자가 @Composable 을 사용해 Kotlin 코드를 작성하면, Compose 컴파일러가 이를 런타임이 이해할 수 있는 형태로 바꾸고 , 런타임은 그 결과를 바탕으로 UI를 그리고 갱신한다. 하지만 이 변환 과정 안을 들여다보면, 생각보다 훨씬 많은 일이 벌어진다. 개발자가 작성하는 것은 단순한 Kotlin 소스 코드이지만, 컴파일 단계에서 Compose 컴파일러는 이 코드를 분석해 중간 표현(IR)을 재구성한다. 이때 런타임에 필요한 매개변수, 그룹 정보, 상태 추적을 위한 각종 메타데이터가 코드에 자동으로 주입된다. 이후 Compose Runtime은 이 변환된 코드를 실행하면서 슬롯 테이블이라는 인메모리 구조를 관리하고, 상태가 바뀌면 필요한 범위만 다시 그리는 Recomposition을 수행한다. 우리가 직접 다루는 Compose UI는 이 구조 위에서 동작하는 여러 클라이언트 중 하나일 뿐이다. 왜 Kotlin 컴파일러 플러그인인가 Compose가 기존의 kapt 기반 어노테이션 프로세서 대신 Kotlin 컴파일러 플러그인 을 선택한 이유는 명확하다. kapt는 컴파일 이전 단계에서 새로운 코드를 생성할 수는 있지만, 이미 작성된 코드의 구조를 바꾸지는 못한다. 반면 컴파일러 플러그인은 컴파일 과정에 직접 개입해 IR을 분석하고 수정할 수 있다. 이 덕분에 Compose는 언어를 “사용”하는 수준을 넘어, 언어의 동작 방식을 확장 하는 선택을 할 수 있었다. 이 방식의 가장 큰 장점은 빠른 피드백이다. Compose 컴파일러는 프론트엔드 단계에서 정적 분석을 수행하며, 잘못된 Composable 호출이나 타입 불일치를 컴파일 시점에 바로 알려준다. 우리가 IDE에서 즉시 보는 경고나 오류는 별도의 IDEA 플러그인이 보조하지만, 그 근본적인 판단은 컴파일러 플러그인에서 이루어진다. 어노테이션은 컴파일러와의 계약이다 Compose에서 어노테...

Composable 함수 이해하기: Jetpack Compose의 핵심 개념 정리

Composable 함수 이해하기: Jetpack Compose의 핵심 개념 정리 Jetpack Compose에서 @Composable 함수는 UI를 직접 그리는 함수가 아니다. 대신 화면이 어떤 구조와 상태를 가져야 하는지를 설명하는 역할 을 한다. 이 설명들은 Compose 런타임에 의해 해석되어 메모리 안의 UI 트리, 즉 Composition 을 구성하거나 업데이트하는 데 사용된다. 이번 글에서는 Composable 함수가 어떤 방식으로 동작하는지, 그리고 왜 특정 규칙들이 중요한지 런타임 관점에서 정리해본다. Composable 함수의 본질: UI를 “그리는 것”이 아닌 “설명하는 것” 일반적인 Kotlin 함수는 입력값을 받아 결과를 반환한다. 반면 Composable 함수는 보통 값을 반환하지 않고( Unit ), 함수 실행 과정에서 UI 구조에 대한 정보를 방출한다. 이 과정은 Compose에서 흔히 emit(방출) 이라고 부르며, “여기에는 텍스트가 필요하고, 그 아래에는 버튼이 있다”와 같은 UI 설계도를 제출하는 것에 가깝다. Compose 런타임은 이 설계도들을 모아 인메모리 UI 트리를 구성한다. 즉, Composable 함수의 유일한 목적은 Composition을 생성하거나 최신 상태로 유지하는 것 이다. @Composable 어노테이션이 하는 일 @Composable 어노테이션은 단순한 표식이 아니다. 이 어노테이션이 붙은 순간, 컴파일러는 해당 함수를 특별한 호출 규칙과 실행 방식 을 가지는 함수로 변환한다. 대표적인 변화 중 하나는 컴파일 시점에 보이지 않는 매개변수인 Composer 가 함수에 추가된다는 점이다. 이 Composer는 Composable 함수와 Compose 런타임 사이의 중재자 역할을 하며, 부모 Composable에서 자식 Composable로 계속 전달된다. 이 구조 때문에 Composable 함수는 오직 다른 Composable 함수 안에서만 호출될 수 있다. Composer가 일관되게 전달되어야 전...