[Compose] Stability

KSang·2024년 4월 30일
1

compose

목록 보기
3/5

컴포저블 함수 매개변수의 클래스 타입

  • Unstable: 데이터 변경 가능, 변경시 컴포지션에서 추적 불가
  • Stable: 데이터 변경 가능, 변경시 컴포지션에서 추적 가능
  • Immutable: 데이터 변경 불가, 안정적인 데이터로 취급

추적이 안되면 데이터를 스킵하는지 하면 안되는지 판단이 안됨
-> 무조건 리컴포지션함

모든 파라미터가 val로 되어있는 data class는 immutable하게 취급

예)

fun ExRow(
	data: ExData,
    modifier: Modifier = Modifier
) {
	var selected by remember { mutableStateOf(false) }
    Row(modifier) {
    	Details(data)
        ToggleButton(
        	selected,
            onToggled = { selected = !selected }
        )
    }
}

data class ExData(
	val name: String,
    val info: String,
)

ExData는 immutable하게 취급

버튼을 누르면 selected의 값이 변경되면서 리컴포지션 되는데, Details는 immutable한 data를 받기 때문에 리컴포지션이 되지 않고 넘어간다.
ToggleButton은 @Stable한 타입이기 때문에 값이 변경되어 리컴포지션 됨

ExData 의 값이 var이면?
val일땐 값을 바꾸려고하면 새로운 객체를 생성해주고 메모리상에서 위치가 바껴 오브젝트에 해당하는 hash값이 변경되어 리컴포지션 되었던거임

하지만 var인 경우 hash값이 변경되지 않음 mutable한 상태임(Unstable)한 상태

이경우 버튼을 눌렀을때 리컴포지션의 대상이 됨

@Stable

@Stable 주석을 추가하면 Compose가 이 유형이 안정적임을 알게 되고 스마트 리컴포지션을 선호하게 됨. 즉 인터페이스가 매개변수 유형으로 사용되는 경우 Compose가 모든 구현을 안정적인 것으로 간주한다.

@Stable 주석을 사용하여 안정적이라고 명시적으로 표시하지 않더라도 Compose 컴파일러가 안정적인 것으로 취급하는 유형

  • 모든 원시 값 유형: Boolean, Int, Long, Float, Char 등
  • 문자열
  • 모든 함수 유형 (람다)

이 유형은 모두 변경할 수 없으므로 stable 계약을 준수할 수 있다.

안정적이지만 변경할 수 있는 한 가지 중요한 유형은 Compose의 MutableState 유형
값이 변경되면 Compose에 알림이 전송되어 상태 객체는 전체적으로 안정적인 것으로 간주되는 것

List

List도 Unstable 클래스다. (Set도)
MutableList로 List를 생성할 수 있다.
List는 MutableList의 인터페이스 변수이기 때문에 immutable하다 보장할 수 없는대,
이를 해결하기위해 2가지 방법이 있다.

  1. @Stable 또는 @Immutable 어노테이션을 사용한다.
@Stable
data class ListState(
	val isLoading: Boolean,
	val data: List<String>,
)

List 콜렉션을 어노테이션을 사용해서 Stable처럼 보이게 한다.
이렇게 되면 값이 변경되지 않는 이상 리컴포지션을 수행하지 않는다.

  1. Immutable Collection 사용하기
    Immutable Collection 깃 저장소
dependencies {
	implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7")
}

의존성을 추가해준다.

ListState(
	isLoading = false,
    data = persistentListOf("A")
)

@Composable
fun Greeting(isLoading: Boolean, data: ImmutableList<String>) {
...

파라미터를 ImmutableList로 변경해주고, listOf를 persistentListOf로 변환해주면 된다.

@StableMaker

@StableMaker에는 @Stable과 @Immutable 가 있다.

@Stable은 Stable이지만 컴파일러가 인식을 못할 때 @Stable을 달면 된다.
@Immutable은 조금 더 엄격한대. 그냥 바뀌지 않는다고 보면 된다.
이는 val을 가진 data class보다 더욱 엄격하다는 것이다.

단, @Immuable과 @Stable의 구현상 컴파일러에서 처리하는 것에 있어 실질적 차이는 아직 없다고 한다.
좀 더 명시적으로 표현할때 쓰면 된다고 한다.

컴파일러 리포트 출력

터미널에 리포트출력을 명령해준다.

./gradlew -P"androidx.enableComposeCompilerReports=true" :app:compileReleaseKotlin

Release 빌드에서 실행해야 정확한 결과값을 얻을 수 있다.

skippable restartable

restartable skippable scheme("[androidx.compose.ui.UiComposable]")
fun SnackCollection(
	stable snackCollection: SnackCollection
    stable onSnackClickl: Function1<Long, Unit>
    stable modifier: Modifier? = @static Companion
    stable index: Int = @static 0
)

composeable.txt 파일에서는 모든 컴포저블 함수에 대해서 분석한 리포트를 내놓는다.

restartable은 어느 부분 부터 리컴포지션이 가능한지 알려주는 것

왜 있냐? 하면 inline 함수 때문인데, inline function은 컴파일러가 바이트코드로 바꿀때 코드레벨이 아닌 기계어나 byteCode에서 inline함수는 실제 함수가 아님
컴파일 하는 시점에 function 내용을 풀어서 일반적인 코드로 작동하기 때문에 restartable이 될수 없음

skippable은 값이 바꼈는지 보고 리컴포지션을 할지 안할지를 나타내는 녀석이다.

모든 파라미터가 stable로 설정되야 skippable 됨, 리컴포지션시 스킵이 가능하다.

restartable scheme("[androidx.compose.ui.UiComposable]")
fun SnackCollection(
	stable snackCollection: SnackCollection
    unstable val tag: List<String>
    stable onSnackClickl: Function1<Long, Unit>
    stable modifier: Modifier? = @static Companion
    stable index: Int = @static 0
)

unstable이 들어있기때문에 skippable하지 않고, 그렇기 때문에 리컴포지션 범위 안에 있을때 무조건 불리게 되어 있음

0개의 댓글