Composer 키우기
메모리상의 트리에 노드가 어떻게 추가되는지 알아보자.
예를 들면 Layout Composable은 Compose UI에서 제공하는 모든 UI 컴포넌트의 기반이 된다.
코드를 살펴보자.
@Suppress(”ComposableLambdaParameterPosition”)
@Composable inline fun Layout(
content: @Composable () ‑> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
) {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
ReusableComposeNode<ComposeUiNode, Applier<Any>>(
factory = ComposeUiNode.Constructor,
update = {
set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
set(density, ComposeUiNode.SetDensity)
set(layoutDirection, ComposeUiNode.SetLayoutDirection)
},
skippableUpdate = materializerOf(modifier),
content = content
)
}
Layout은 LayoutNode를 composition에 방출하기 위해 ReusableComposeNode를 사용한다. 이런 동작은 바로 실행되는 것이 아닌, 적절한 시기에 composition의 현재 위치에서 노드를 생성, 초기화 및 삽입하는 방법을 Compose Runtime에 알려준다.
그러한 동작을 나타내는 코드를 살펴보자.
@Composable
inline fun <T, reified E : Applier<*>> ReusableComposeNode(
noinline factory: () ‑> T,
update: @DisallowComposableCalls Updater<T>.() ‑> Unit,
noinline skippableUpdate: @Composable SkippableUpdater<T>.() ‑> Unit,
content: @Composable () ‑> Unit
) {
// ...
currentComposer.startReusableNode()
// ...
currentComposer.createNode(factory)
// ...
Updater<T>(currentComposer).update() // initialization
// ...
currentComposer.startReplaceableGroup(0x7ab4aae9)
content()
currentComposer.endReplaceableGroup()
currentComposer.endNode()
}
사실 위 코드에서 생략된 부분도 조금은 있지만, currentComposer 인스턴스에 모든 것을 위임하는 것에 중점을 둬야한다.
content 람다식 내부에서 방출된 모든 자식들은 실제로 composition에서 교체 가능한 그룹의 자식으로서 저장된다.
다른 Composable 함수에 대해서도 동일한 방출 작업이 수행되며, 그중 remember에 대해 알아보자.
@Composable
inline fun <T> remember(calculation: @DisallowComposableCalls () ‑> T): T =
currentComposer.cache(invalid = false, calculation)
remember Composable 함수는 제공된 람다식에 의해 반환된 값을 composition에 캐싱하기 위해 currentComposer을 참조한다.
invalid 매개변수는 사전에 값이 저장되어 있더라도 값을 강제로 업데이트 여부를 지정하기 위한 값이다.
currentComposer의 cache 함수는 아래와 같이 구현되어 있다.
@ComposeCompilerApi
inline fun <T> Composer.cache(invalid: Boolean, block: () ‑> T): T {
return rememberedValue().let {
if (invalid ││ it === Composer.Empty) {
val value = block()
updateRememberedValue(value)
value
} else it
} as T
}
먼저 composition에서 값을 찾아보고, 없을 경우 해당 값에 대한 업데이트를 예약하기 위해 변경 사항을 방출한다.
반대로 존재할 경우엔 있는 값 그대로를 반환한다.
변경사항 모델링
윗글에서 봤듯이 currentComposer에 위임된 모든 방출 작업은 Change로 모델링 되어 내부 변경 목록에 추가된다.
Change는 현재의 Applier 및 SlotWriter에 액세스 할 수 있는 지연 함수이다.
Change의 구현에 대해 살펴보자.
internal typealias Change = (
applier: Applier<*>,
slots: SlotWriter,
rememberManager: RememberManager
) ‑> Unit
변경 사항들은 변경 목록에 추가된다. 앞서 계속해서 언급했듯이 방출이라는 행위는 본질적으로 이러한 Change들을 생성하는 것을 의미하며 결과적으로 Applier에 변경 사항들을 알리는 것을 의미한다.
Composition이 끝나면 일련의 Composable 함수들이 호출되고, 모든 변경 사항이 기록된 후, Applier에 의해 모든 변경 사항들이 일괄적으로 적용된다.
'Android' 카테고리의 다른 글
[Android] Compose 런타임 - (5) (0) | 2024.11.29 |
---|---|
[Android] Compose 런타임 - (4) (0) | 2024.11.28 |
[Android] Compose 런타임 - (2) (0) | 2024.11.26 |
[Android] Compose 런타임 - (1) (0) | 2024.11.25 |
[Android] Compose 컴파일러 - (8) (0) | 2024.11.22 |