Compose의 안정성

Compose는 유형을 안정적 또는 불안정적으로 간주합니다. 변경 불가능하거나 Compose에서 값이 재구성 간에 변경되었는지 알 수 있으면 안정적인 유형입니다. Compose에서 값이 재구성 간에 변경되었는지 알 수 없으면 불안정한 유형입니다.

Compose는 컴포저블 매개변수의 안정성을 사용하여 입력을 비교하는 방법을 결정하고 리컴포지션 중에 컴포저블을 건너뛸 수 있는지 결정합니다 (Kotlin 2.0.20부터 기본적으로 강력한 건너뛰기 모드가 사용 설정됨).

  • 안정적인 매개변수: Compose는 구조적 동등성 (Object.equals())을 사용하여 안정적인 매개변수를 비교합니다. 컴포저블의 안정적인 매개변수가 이전 값과 같으면 Compose는 이를 건너뜁니다.
  • 불안정한 매개변수: 강력한 건너뛰기 모드가 사용 설정된 경우 (Kotlin 2.0.20 이상의 기본값) Compose는 인스턴스 동등성 (===)을 사용하여 불안정한 매개변수를 비교하고 동일한 객체 인스턴스가 전달되면 컴포저블을 건너뜁니다. Kotlin 2.0.20 이전 버전 (또는 강력한 건너뛰기 모드가 사용 중지된 경우)에서는 상위 요소가 재구성될 때 Compose가 항상 불안정한 매개변수가 있는 컴포저블을 재구성합니다.

앱이 불안정한 매개변수의 새 인스턴스를 자주 할당하거나 모델에 과도하게 주석을 달아 대규모 컬렉션에 비용이 많이 드는 .equals() 검사를 강제하는 경우 불필요한 리컴포지션이나 비교 오버헤드가 발생할 수 있습니다.

이 문서에서는 Compose가 안정성을 결정하는 방법과 성능 및 전반적인 사용자 환경을 개선하기 위해 안정성을 최적화하는 방법을 자세히 설명합니다.

변경 불가능한 객체

다음 스니펫은 안정성과 리컴포지션의 일반적인 원리를 보여줍니다.

Contact 클래스는 변경 불가능한 데이터 클래스입니다. 모든 매개변수가 val 키워드로 정의된 기본 요소이기 때문입니다. Contact의 인스턴스를 만들고 나면 객체 속성 값을 변경할 수 없습니다. 그렇게 하려고 하면 새 객체가 생성됩니다.

data class Contact(val name: String, val number: String)

ContactRow 컴포저블에는 Contact 유형의 매개변수가 있습니다.

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

사용자가 전환 버튼을 클릭하고 selected 상태가 변경되면 어떻게 되는지 생각해 보세요.

  1. Compose는 ContactRow 내의 코드를 재구성해야 하는지 평가합니다.
  2. ContactDetails의 유일한 인수가 Contact 유형임을 확인합니다.
  3. Contact는 변경 불가능한 데이터 클래스이므로 Compose는 ContactDetails의 인수가 변경되지 않았음을 확신합니다.
  4. 따라서 Compose는 ContactDetails을 건너뛰고 재구성하지 않습니다.
  5. 반면 ToggleButton의 인수는 변경되었으며 Compose는 이 구성요소를 재구성합니다.

변경 가능한 객체

앞의 예에서는 변경할 수 없는 객체를 사용하지만 변경 가능한 객체를 만들 수 있습니다. 다음 스니펫을 살펴보세요.

data class Contact(var name: String, var number: String)

이제 Contact의 각 매개변수가 var이므로 클래스는 더 이상 변경할 수 없습니다. 속성이 변경되어도 Compose는 이를 인식하지 못합니다. Compose는 Compose State 객체의 변경사항만 추적하기 때문입니다.

Compose는 이러한 클래스를 불안정하다고 간주합니다. 강력한 건너뛰기 모드(Kotlin 2.0.20 이상)를 사용하면 Compose는 인스턴스 동등성 (===)을 사용하여 Contact를 비교합니다. contact.name를 제자리에서 변경해도 리컴포지션이 트리거되지 않지만, 동일한 값을 가진 새로 할당된 Contact 인스턴스를 전달하면 여전히 ContactDetails가 강제로 리컴포지션됩니다 (강력한 건너뛰기 모드가 없으면 ContactDetails는 selected가 변경될 때마다 리컴포지션됨).

Compose에서 구현

Compose가 리컴포지션 중에 건너뛸 함수를 정확히 어떻게 결정하는지 고려하는 것이 중요하지는 않지만 도움이 될 수 있습니다.

Compose 컴파일러가 코드에서 실행되면 각 함수와 유형을 여러 태그 중 하나로 표시합니다. 이러한 태그는 리컴포지션 중에 Compose가 함수 또는 유형을 처리하는 방식을 반영합니다.

함수

Compose는 함수를 skippable 또는 restartable로 표시할 수 있습니다. 함수를 다음 중 하나, 둘 다 또는 둘 다 아닌 것으로 표시할 수 있습니다.

  • 건너뛸 수 있음: 컴파일러가 컴포저블을 건너뛸 수 있는 것으로 표시하면 Compose는 리컴포지션 중에 모든 인수가 이전 값과 동일한 경우 이를 건너뛸 수 있습니다. (Kotlin 2.0.20 이상의 강력한 건너뛰기 모드를 사용하면 다시 시작 가능한 모든 컴포저블을 자동으로 건너뛸 수 있습니다.)
  • 다시 시작 가능: 다시 시작 가능한 컴포저블은 리컴포지션을 시작할 수 있는 '범위' 역할을 합니다. 즉, 이 함수는 상태 변경 후 Compose가 리컴포지션을 위해 코드 재실행을 시작할 수 있는 진입점이 될 수 있습니다.

유형

Compose는 유형을 변경 불가능 또는 안정적 중 하나로 표시합니다. 각 유형은 다음 중 하나입니다.

  • 변경 불가: Compose는 속성 값이 변경될 수 없고 모든 메서드가 참조 투명성을 갖는 경우 유형을 변경 불가로 표시합니다.
    • 모든 기본 유형은 변경 불가능으로 표시됩니다. 여기에는 String, Int, Float가 포함됩니다.
  • 안정적: 생성 후 속성이 변경될 수 있는 유형을 나타냅니다. 런타임 중에 이러한 속성이 변경되면 Compose는 이러한 변경사항을 인식합니다.

디버그 안정성

앱이 매개변수가 변경되지 않은 컴포저블을 재구성하는 경우 먼저 재구성할 때마다 불안정한 유형의 새 인스턴스가 할당되는지 확인합니다 (또는 강력한 건너뛰기 모드가 사용 중지된 경우 컴포저블에 불안정한 유형의 var 속성 또는 val 속성이 있는 매개변수가 있는지 확인).

Compose에서 안정성과 관련된 복잡한 문제를 진단하는 방법에 관한 자세한 내용은 안정성 디버그 가이드를 참고하세요.

안정성 문제 해결

Compose 구현에 안정성을 제공하는 방법에 관한 자세한 내용은 안정성 문제 해결 가이드를 참고하세요.

요약

전반적으로 다음 사항에 유의해야 합니다.

  • 매개변수: Compose는 컴포저블의 각 매개변수의 안정성을 확인하여 리컴포지션 중에 구조적 동등성(.equals()) 또는 인스턴스 동등성 (===)을 사용하여 비교할지 결정합니다.
  • 즉각적인 수정: 컴포저블이 건너뛰어지지 않고 성능 문제를 일으키는 경우 각 패스에서 불안정한 매개변수의 새 인스턴스가 다시 생성되는지 또는 State 대신 var 속성이 사용되는지 확인합니다.
  • 컴파일러 보고서: 컴파일러 보고서를 사용하여 클래스에 관해 추론되는 안정성을 확인할 수 있습니다.
  • 컬렉션: Compose는 표준 컬렉션 인터페이스 (List, Set, Map)를 불안정하다고 간주합니다. 기본 구현이 변경 가능할 수 있기 때문입니다. 강력한 건너뛰기 모드 (Kotlin 2.0.20 이상에서 기본적으로 사용 설정됨)를 사용하면 불안정한 컬렉션은 컬렉션 참조가 변경되지 않을 때 인스턴스 동등성 (===)을 사용하여 재구성을 건너뜁니다. 표준 컬렉션을 Kotlinx 변경 불가능한 컬렉션으로 바꾸거나 컬렉션 보유 클래스에 @Immutable 또는 @Stable 주석을 달 때는 주의해야 합니다. 각 리컴포지션 패스에서 모든 요소를 .equals()와 비교하는 것이 리컴포지션 자체보다 비용이 더 많이 들 수 있기 때문입니다.
  • 기타 모듈: Compose는 Compose 컴파일러가 실행되지 않는 모듈의 클래스를 불안정하다고 간주합니다 (강력한 건너뛰기 모드에서 ===를 사용하여 비교됨). 데이터 소스가 동일한 값을 가진 Compose가 아닌 모듈에서 작고 평평한 모델을 자주 다시 인스턴스화하는 경우 안정성 구성 파일을 구성하거나 .equals()가 저렴한 @Stable 또는 @Immutable를 사용할 수 있습니다.

추가 자료