[Android Jetpack Compose] Compose - Stable

Nilto·2023년 6월 7일
0

Compose_Internals

목록 보기
1/1

Compose에서 Stable하다는 개념은 매우 중요한 개념이다.

Stable이 필요한 이유는 Smart Recomposition 때문이다. Smart Recomposition은 리컴포지션을 진행할 때, 모든 input이 stable이고 같은 값이라면 skip이 되게끔 하는 것이다. 이말은, 바뀐게 없으면 바꾸지 않겠다를 좀 더 효율적으로 한다는 말이다. 만약 바뀌지 않았다는 확신(certain)이 없다면 무조건 바꾼다.

그러면, Stable하다는 것은 어떤것일까?

Stable

공식 문서에서는 Stable에 대한 3가지 조건을 제시한다.

  • The result of equals for two instances will forever be the same for the same two instances.
    두 인스턴스의 equals가 같아야한다. 이는 짐짓 당연해 보이는 내용이다.

  • If a public property of the type changes, Composition will be notified.
    이 경우는 public property에 Snapshot의 State를 쓴 경우이다. State가 바뀌면 Composition will be notified 되기 때문이다.

  • All public property types are also stable.
    모든 public property가 Stable이어야한다.

또한, 다음 3가지는 기본적으로 Stable로 취급한다.

  • All primitive value types: Boolean, Int, Long, Float, Char, etc.

  • Strings

  • All Function types (lambdas)

여기서 Function types, 즉 람다가 왜 Stable인지 헷갈릴 수 있다. mutable한 값을 캡쳐된 람다도 Stable로 취급될까? 정답은 취급된다이다.

그러나 다음과 같은 케이스는 없을까? (물론 실제로 이런 코드를 작성할 일은 없을 것이다.)

class TestClass {
    val test: @Composable () -> Unit = {
        Text(x)
    }
}
var x = "not init text"

@Composable
fun TestTest(testClass: TestClass) {
    testClass.test()
}

캡쳐링 관련이 아니라 해도 이런 코드도 가능할 것이다.

class TestClass {
    val test: @Composable () -> Unit = {
        var x = "not init text"
        AnyGlobalObject.addAction {
            x += "!!"
        }
        Text(x)
    }
}

이런 코드에서라면 notify가 되지 않고서도 바뀔 수 있다. 그러면 unstable이 되어야하지 않을까?
이 부분에 대해 의문이 생겨 슬랙 Kotlin 채널 Compose방에 물어봤으나, 명확한 대답은 들어볼 수 없었다. (혹시 아시는 분은 알려주시면 감사할 것 같다.)

List도 Unstable?

기본적으로 List도 Unstable 클래스이다. 왜냐? 단순한 이유다. List에는 MutableList를 넣을 수 있기 때문이다. MutableList가 Stable하지 않기 때문에 List도 Unstable이게 되는 것이다.
그래서 이런 문제를 해소하기 위해 Immutable Collection를 사용할 수도 있다.
(혹은 확실히 List만 넣는다면 @Stable을 사용할 수도 있을 것이다)

Inferring class stability

그러나 컴파일러는 어떤 타입이 Stable인지 알 수 없다. 정확히 말해서는 타입 추론을 통해 Stable인지 파악해보기는 하지만, 완벽하지 않다. 컴파일러는 2가지로 판단한다.

  • The field is mutable (it is associated with a var property)

  • The field has a non-stable type

그러나 이런 판단 기준은 앞서 말했듯 완벽하지 않다. 실제로는 Stable의 조건을 가지고 있지만 컴파일러가 인식하질 못하면 Smart Recomposition이 발동하지 않는다. 이럴 때 @StableMaker 어노테이션을 사용한다.

@StableMaker

@StableMaker에는 2가지가 있다. @Stable과 @Immutable이다.

@Stable은 Stable이지만 컴파일러가 인식을 못할 때 @Stable을 달면 된다고 보면 간단하다. 그러나 @Immutable은 조금 더 엄격한 약속이라고 보면 된다.(strict promise) 그냥 바뀌지 않는다고 보면 된다. 이는 val을 가진 data class보다 더욱 엄격하다는 것이다. (val MutableList를 생각해보라)

단, @Immuable과 @Stable의 구현상 컴파일러에서 처리하는 것에 있어 실질적 차이는 아직 없다고 한다. 그러나 앞으로 실제 구현이 다르게끔 바뀔 수도 있고, 명시적인게 좋으니 확실하게 써두도록 하자.

끝으로

그러면 이러한 것들을 통해 실제로 런타임이 최적화를 어떻게 해내는지에 대해 의문을 가질 수 있다. 그러면 다음 문서 또한 추가로 보도록 하자. (문서 작성중)

또한, 아래 링크에 있는 툴을 사용하면 compiler가 report를 해줘서 특정 클래스가 컴파일러에서 Stable로 인식하는지 Unstable로 인식하는지 확인해볼 수 있다. 잘 와닿지 않으면 val, var 등을 하나하나 바꿔가면서 확인해보도록 하자.
https://github.com/androidx/androidx/blob/androidx-main/compose/compiler/design/compiler-metrics.md

Reference

https://developer.android.com/jetpack/compose/lifecycle#skipping

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt

https://www.youtube.com/watch?v=bg0R9-AUXQM

https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8

profile
안드로이드 개발자. 컴포즈, 코루틴, 코틀린, 유니티에 관심이 많습니다.

0개의 댓글