Compiler는 Composable 함수의 본문에 group을 삽입한다.
Composable 함수는 런타임 시에 그룹을 생성하며, 생성된 그룹들은 Composable 함수의 현 상태에 대한 모든 정보를 감싸고 보존한다. 이를 통해 composition은 그룹의 교체가 필요할때, 정체성을 유지하며 데이터를 이동시킬때, recomposition중에 함수를 재시작할 때 쓰여진 데이터를 어떻게 처리해야 할지 알 수 있다.
또한, 그룹은 Composable 함수의 호출 위치에 대한 정보를 가지고 있다. 이를 통해 그룹을 저장하고, 위치 기억법을 가능하게 한다.
Composable 람다식을 기억할때 Composable 람다식의 본문에 $composer, $key, 람다식의 실체와 같은 값을 매개변수로 받는 Composable 팩토리 함수 호출을 삽입해 composable 람다식이 자동으로 감싸진다고 했다.
@Suppress("unused")
@ComposeCompilerApi
public fun composableLambda(
composer: Composer,
key: Int,
tracked: Boolean,
block: Any,
): ComposableLambda {
// 함수의 키와 람다의 키가 중복되는 것을 피하기 위해, (원본 키를) 변형한(rolled) 버전의 키를 사용합니다.
// 이는 그룹이 키 번호에 의해 무효화(invalidated)되는 Live Edit 시나리오에서 특히 중요합니다.
// 이를 통해 함수가 무효화되더라도 그 내부의 람다까지 같이 무효화되지 않도록 보장합니다.
composer.startMovableGroup(key.rol(1), lambdaKey)
val slot = composer.rememberedValue()
val result =
if (slot === Composer.Empty) {
val value = ComposableLambdaImpl(key, tracked, block)
composer.updateRememberedValue(value)
value
} else {
slot as ComposableLambdaImpl
slot.update(block)
slot
}
composer.endMovableGroup()
return result
}
private val lambdaKey = Any()
먼저 key로 이동 가능한 그룹을 시작하고, 중간에 모든 텍스트의 범위를 감싸고, 마지막으로 그룹을 닫는다. 시작과 끝 호출 사이에서, composition을 관련성 있는 정보로 업데이트한다.
위의 예시처럼 Composable 람다식뿐만 아니라 다른 Composable 함수의 호출 또한 동일하게 처리된다.
@NonRestartableComposable
@Composable
fun Foo(x: Int) {
Wat()
}
// Decompile
public static final void Foo(int x, @Nullable Composer $composer, int $changed) {
$composer.startReplaceGroup(-1621382871);
Wat($composer, 0)
$composer.endReplaceGroup();
}
위의 Composable 함수 또한 composition에 저장하기 위한 교체 가능한 그룹을 생성한다. 그룹은 일종의 트리와 같다. 각 그룹은 원하는만큼 그 어떠한 수의 그룹도 자식으로 가질 수 있다. 위의 코드에서 Wat() 함수 또한 Composable이라면, 컴파일러는 해당 함수에 대해서도 그룹을 삽입할 것이다.
$composer.startReplaceableGroup(key_foo) // Foo의 시작
// Foo의 로직
$composer.startReplaceableGroup(key_wat) // Wat의 시작
// Wat의 로직 (예: Text 호출 등)
$composer.startReplaceableGroup(key_text)
$composer.endReplaceableGroup()
$composer.endReplaceableGroup() // Wat의 끝
$composer.endReplaceableGroup() // Foo의 끝
Composable 호출은 위치에 기반하기 때문에 정체성이 보존될 수 있다고 “Composable 어노테이션” 섹션에서 살펴보았다. 런타임은 아래의 서로 다른 두 Text 호출이 다르다는 것을 이해할 수 있다.
@Composable
fun ConditionText() {
if (condition) {
Text(text = "Hello")
} else {
Text(text = "World")
}
}
// Decompile
if (condition) {
$composer.startReplaceGroup(15449722);
ComposerKt.sourceInformation($composer, "21@449L20");
TextKt.Text--4IGK_g("Hello", ...);
$composer.endReplaceGroup();
} else {
$composer.startReplaceGroup(15491386);
ComposerKt.sourceInformation($composer, "23@491L20");
TextKt.Text--4IGK_g("World", ...);
$composer.endReplaceGroup();
}
이와 같은 조건부 논리를 수행하는 Composable 함수 또한 교체 가능한 그룹을 발행하는데, 조건이 전환되면 교체될 수 있는 그룹을 슬롯 테이블에 저장한다.