• 목록 (128)
    • Android (62)
    • Back-End (2)
    • Java (3)
    • Kotlin (16)
    • CS (7)
    • 개발 서적 (12)
    • 문제 풀이 (26)

최근 글

티스토리

전체 방문자
오늘
어제
hELLO · Designed By 정상우.
MJ_94

한 우물만 파는 기술 블로그

Android

[Android] Compose 컴파일러 - (6)

2024. 11. 20. 16:11

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
    'Android' 카테고리의 다른 글
    • [Android] Compose 컴파일러 - (8)
    • [Android] Compose 컴파일러 - (7)
    • [Android] Compose 컴파일러 - (5)
    • [Android] Compose 컴파일러 - (4)
    MJ_94
    MJ_94
    안드로이드, 개발 관련 기술 블로그

    티스토리툴바