Composable 람다식
Compose Compiler는 Composable의 람다식을 기억한다.
@Composable
fun Container(content: @Composable () -> Unit) {
}
위와 같이 선언될 경우 람다식이 IR 과정에 의해 특수한 매개변수들이 추가될 것이고 이를 통해 Composable 팩토리 함수인 composableLamda()를 호출하도록 수정될 것이다.
특수한 매개변수란 첫 번째로, 현재 컨텍스트의 $composer이고 composableLamda($composer, …)와 같은 형태로 매개변수가 전달된다.
그 다음으로는 Composable 람다식의 정규화된 이름으로 생성한 해시코드와 표현식이 파일 내에서 위치하는 지점의 조합으로 얻은 key 매개변수를 추가한다.
이를 통해 key가 위치 기억법을 사용할 때 composableLamda($composer, $key, …)과 같은 형태로 고유성을 부여하기 위함이다.
key 다음으로는 shoudeBeTracked라는 boolean 타입의 매개변수가 추가되어 composableLamda($composer, $key, $shoudeBeTracked, …)과 같은 형태를 가지게 되는데, 이 매개변수는 해당 Composable 람다 호출이 추적이 필요한지에 대한 값이다.
표현식의 개수에 대한 optional 매개변수도 composableLamda($composer, $key, $shoudeBeTracked, &arity, …)와 같은 형태로 추가될 수 있으며, 이는 매개변수가 22개를 넘길 경우에만 필요하다고 한다.
마지막으로는 매개변수로 후행 람다식 자체를 추가하여 composableLamda($composer, $key, $shoudeBeTracked, &arity, expression)과 같은 형태로 표현된다.
Composer 주입하기
이 단계는 Compose Compiler가 모든 Composable 함수에 Composer라는 합성 매개변수를 추가하여 기존의 Composable 함수들을 완전히 대체한다.
앞에서도 여러 번 언급했지만 이 매개변수는 코드의 모든 Composable 함수 호출에 전달되어 트리의 어느 지점에서나 항상 사용할 수 있도록 한다. 바로 위에서 다룬 Composable의 람다식 호출도 포함이다.
fun NamePlate(name: String, lastname: String, $composer: Composer) {
$composer.start(123)
Column(modifier = Modifier.padding(16.dp), $composer) {
Text(
text = name,
$composer
)
Text(
text = lastname,
style = MaterialTheme.typography.subtitle1,
$composer
)
}
$composer.end()
}
비교 전파
Compose Compiler가 $composer 매개변수를 어떤 식으로 주입하고, 어떻게 Composable에 전달하는지 여러번 다뤘지만, 사실 Composable 함수마다 추가되는 몇 가지 추가 메타데이터가 있다.
그중 하나는 $changed 매개변수로 현재 Composable의 입력 매개변수가 이전 composition 이후에 변경된 적이 있는지에 대한 단서를 제공하며 Runtime은 이를 통해 recomposition을 생략할 수 있다.
@Composable
fun Header(text: String, $composer: Composer<*>, $changed: Int)
$changed 매개변수는 함수의 입력 매개변수 각각에 대하여 아래와 같은 조건을 가지는 비트의 합성으로 구성된다.
- 모든 n개의 입력 매개변수에 대한 조건을 인코딩하는 단일 $changed 매개 변수가 있으며, 이는 사용되는 비트 크기에 의해 제한된다.
- Composable 함수에 매개변수가 스펙보다 더 많을 경우 이를 모두 추적하기 위해 2개 이상의 $changed 매개변수가 추가된다.
비트값을 쓰는 이유는 프로세서 구조상 비트 연산이 가지는 장점이 많기 때문이다.
이런 메타데이터를 전달하여 Runtime에 대해 부분 최적화가 가능하다.
- 입력 매개변수가 최근 변경된 값인지 확인하기 위해 equals() 함수를 통한 비교를 생략한다. 이는 매개변수가 정적인 경우 수행되며 $changed라는 비트마스크 매개변수를 통해 해당 정보가 제공된다.
- 매개변수가 마지막 composition 수행 이후 변경되지 않거나, 변경된 경우 상위 트리의 Composable에 의해 매개변수 변경에 대한 비교작업이 이미 수행된 것으로 보장되는 경우.
- Runtime은 지속적으로 equals() 함수를 이용하여 매개변수 값을 비교하고, 슬롯 데이블에 저장해 놓음으로써 늘 최신 상태를 유지한다. 이 경우의 비트 값은 디폴트 값(0)이고, $changed 매개변수에 0이 전달되면 Runtime에게 모든 매개변수 비교 작업을 수행하도록 지시한다.
아래의 코드는 Composable 함수의 본문에 $changed 매개변수와 이를 처리하기 위한 로직을 주입한 이후의 모습이다.
@Composable
fun Header(text: String, $composer: Composer<*>, $changed: Int) {
var $dirty = $changed
if ($changed and 0b0110 === 0) {
$dirty = $dirty or if ($composer.changed(text)) 0b0010 else 0b0100
}
if ($dirty and 0b1011 xor 0b1010 !== 0 || !$composer.skipping) {
f(text)
} else {
$composer.skipToGroupEnd()
}
}
비트값을 일부분 조정하지만, 저수준에서 구체적인 구현을 모르는 상태로 유지하기 위해 로컬 변수 $dirty가 사용되고 있다.
$dirty 변수는 매개변수가 변경되었지에 대한 여부를 저장하며, 이는 $changed 매개변수의 마스킹과 슬롯 테이블에 저장된 이전 값에 의해 결정된다.
매개변수의 값이 dirty 하다고 간주되면 함수의 본문이 호출되고 recomposition이 발생하고 그렇지 않으면 recomposition을 생략한다.
'Android' 카테고리의 다른 글
[Android] Compose 컴파일러 - (8) (0) | 2024.11.22 |
---|---|
[Android] Compose 컴파일러 - (7) (0) | 2024.11.21 |
[Android] Compose 컴파일러 - (5) (0) | 2024.11.19 |
[Android] Compose 컴파일러 - (4) (0) | 2024.11.18 |
[Android] Compose 컴파일러 - (3) (0) | 2024.11.15 |