성능 문제를 일으키는 불안정한 클래스가 있는 경우 안정적으로 만들어야 합니다. 이 문서에서는 이를 수행하는 데 사용할 수 있는 여러 기법을 간략히 설명합니다.
강력한 건너뛰기 사용 설정
먼저 강력한 건너뛰기 모드를 사용 설정해 보세요. 강력한 건너뛰기 모드를 사용하면 불안정한 매개변수가 있는 컴포저블을 건너뛸 수 있으며 안정성으로 인해 발생하는 성능 문제를 해결하는 가장 쉬운 방법입니다.
자세한 내용은 강력한 건너뛰기를 참고하세요.
클래스를 변경 불가능하게 만들기
불안정한 클래스를 완전히 변경 불가능하게 만들 수도 있습니다.
- 변경 불가능: 유형의 인스턴스가 생성된 후에는 속성 값을 변경할 수 없으며 모든 메서드가
참조적으로 투명한 유형을 나타냅니다.
- 클래스의 모든 속성이
var가 아닌val이고 변경 불가능한 유형인지 확인합니다. String, Int,Float와 같은 기본 유형은 항상 변경 불가능합니다.- 이것이 불가능한 경우 변경 가능한 속성에 Compose 상태를 사용해야 합니다.
- 클래스의 모든 속성이
- 안정적: 변경 가능한 유형을 나타냅니다. Compose 런타임은 유형의 공개 속성 또는 메서드 동작이 이전 호출과 다른 결과를 생성하는지 여부와 시기를 인식하지 못합니다.
변경 불가능한 컬렉션
Compose에서 클래스를 불안정하다고 간주하는 일반적인 이유는 컬렉션입니다. 안정성 문제 진단 페이지에 설명된 대로 Compose 컴파일러는 List, Map, Set과 같은 컬렉션이 실제로 변경 불가능한지 완전히 확신할 수 없으므로 불안정하다고 표시합니다.
이 문제를 해결하려면 변경 불가능한 컬렉션을 사용하면 됩니다. Compose 컴파일러 에는 Kotlinx 변경 불가능한 컬렉션 지원이 포함되어 있습니다. 이러한 컬렉션은 변경 불가능하도록 설계되었으며 Compose 컴파일러는 이를 변경 불가능한 것으로 취급합니다. 이 라이브러리는 아직 알파 버전이므로 API가 변경될 수 있습니다.
안정성 문제 진단 가이드의 불안정한 클래스를 다시 살펴보세요.
unstable class Snack {
…
unstable val tags: Set<String>
…
}
변경 불가능한 컬렉션을 사용하여 tags를 안정적으로 만들 수 있습니다. 클래스에서
유형을 tags으로 변경합니다ImmutableSet<String>.
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
이렇게 하면 클래스의 모든 매개변수가 변경 불가능해지고 Compose 컴파일러는 클래스를 안정적이라고 표시합니다.
Stable 또는 Immutable로 주석 달기
안정성 문제를 해결하는 한 가지 방법은 불안정한 클래스에 @Stable 또는 @Immutable로 주석을 추가하는 것입니다.
클래스에 주석을 추가하면 컴파일러가 클래스에 관해 추론하는 내용을 재정의합니다. Kotlin의
!! 연산자와 비슷합니다. 이러한 주석을 사용하는 방법에 매우 주의해야 합니다. 컴파일러 동작을 재정의하면 예상대로 컴포저블이 리컴포지션되지 않는 등 예기치 않은 버그가 발생할 수 있습니다.
주석 없이 클래스를 안정적으로 만들 수 있다면 안정성을 달성하기 위해 노력해야 합니다.
다음 스니펫은 변경 불가능한 것으로 주석이 추가된 데이터 클래스의 최소한의 예를 제공합니다.
@Immutable
data class Snack(
…
)
@Immutable 또는 @Stable 주석을 사용하는지 여부에 관계없이 Compose 컴파일러는 Snack 클래스를 안정적이라고 표시합니다.
컬렉션의 주석이 추가된 클래스
List<Snack> 유형의 매개변수를 포함하는 컴포저블을 고려해 보세요.
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Snack에 @Immutable로 주석을 추가하더라도 Compose 컴파일러는 여전히
HighlightedSnacks의 snacks 매개변수를 불안정하다고 표시합니다.
매개변수는 컬렉션 유형과 관련하여 클래스와 동일한 문제에 직면합니다.
Compose 컴파일러는 안정적인 유형의 컬렉션인 경우에도 항상 List 유형의 매개변수를 불안정하다고 표시합니다.
개별 매개변수를 안정적이라고 표시할 수 없으며 항상 건너뛸 수 있도록 컴포저블에 주석을 추가할 수도 없습니다. 앞으로 나아갈 수 있는 방법은 여러 가지가 있습니다.
불안정한 컬렉션 문제를 해결하는 방법은 여러 가지가 있습니다. 다음 하위 섹션에서는 이러한 다양한 접근 방식을 간략히 설명합니다.
구성 파일
변경 불가능한 컬렉션
변경 불가능성의 컴파일 시간 안전을 위해 List 대신 kotlinx 변경 불가능한 컬렉션을 사용할 수 있습니다.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
래퍼
변경 불가능한 컬렉션을 사용할 수 없는 경우 직접 만들 수 있습니다. 이렇게 하려면 주석이 추가된 안정적인 클래스에서 List를 래핑합니다. 일반 래퍼는 요구사항에 따라 이 작업에 가장 적합한 선택일 수 있습니다.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
그런 다음 이를 컴포저블의 매개변수 유형으로 사용할 수 있습니다.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
솔루션
이러한 접근 방식 중 하나를 취한 후 Compose 컴파일러는 이제 HighlightedSnacks 컴포저블을 skippable 및 restartable로 표시합니다.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int
stable snacks: ImmutableList<Snack>
stable onSnackClick: Function1<Long, Unit>
stable modifier: Modifier? = @static Companion
)
이제 리컴포지션 중에 입력이 변경되지 않은 경우 Compose는 HighlightedSnacks를 건너뛸 수 있습니다.
안정성 구성 파일
Compose 컴파일러 1.5.5부터 컴파일 시간에 안정적인 것으로 간주할 클래스의 구성 파일을 제공할 수 있습니다. 이렇게 하면 LocalDateTime과 같은 표준 라이브러리 클래스와 같이 제어할 수 없는 클래스를 안정적인 것으로 간주할 수 있습니다.
구성 파일은 행당 하나의 클래스가 있는 일반 텍스트 파일입니다. 주석, 단일, 이중 와일드 카드가 지원됩니다.
구성 예:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>
이 기능을 사용 설정하려면 구성 파일의 경로를
composeCompiler Compose 컴파일러 Gradle 플러그인
구성의 옵션 블록에 전달합니다.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Compose 컴파일러는 프로젝트의 각 모듈에서 별도로 실행되므로 필요한 경우 여러 모듈에 여러 구성을 제공할 수 있습니다. 또는 프로젝트의 루트 수준에 하나의 구성을 두고 각 모듈에 해당 경로를 전달합니다.
다중 모듈
또 다른 일반적인 문제는 다중 모듈 아키텍처와 관련이 있습니다. Compose 컴파일러는 참조하는 모든 비기본 유형이 안정적인 것으로 명시적으로 표시되거나 Compose 컴파일러로 빌드된 모듈에 있는 경우에만 클래스가 안정적인지 추론할 수 있습니다.
데이터 레이어가 UI 레이어와 별도의 모듈에 있는 경우(권장되는 접근 방식) 이 문제가 발생할 수 있습니다.
솔루션
이 문제를 해결하려면 다음 접근 방식 중 하나를 취하면 됩니다.
- 클래스를 컴파일러 구성 파일에 추가합니다.
- 데이터 레이어 모듈에서 Compose 컴파일러를 사용 설정하거나 적절한 경우 클래스에
@Stable또는@Immutable로 태그를 지정합니다.- 여기에는 데이터 레이어에 Compose 종속 항목을 추가하는 것이 포함됩니다. 그러나 이는
Compose-UI가 아닌 Compose 런타임의 종속 항목일 뿐입니다.
- 여기에는 데이터 레이어에 Compose 종속 항목을 추가하는 것이 포함됩니다. 그러나 이는
- UI 모듈 내에서 데이터 레이어 클래스를 UI별 래퍼 클래스로 래핑합니다.
외부 라이브러리가 Compose 컴파일러를 사용하지 않는 경우에도 동일한 문제가 발생합니다.
모든 컴포저블을 건너뛸 수 있는 것은 아님
안정성 문제를 해결하기 위해 노력할 때는 모든 컴포저블을 건너뛸 수 있도록 시도해서는 안 됩니다. 이렇게 하면 해결하는 것보다 더 많은 문제를 일으키는 조기 최적화가 발생할 수 있습니다.
건너뛸 수 있는 것이 실제로 이점이 없고 유지보수가 어려운 코드를 초래할 수 있는 상황이 많이 있습니다. 예를 들면 다음과 같습니다.
- 자주 또는 전혀 리컴포지션되지 않는 컴포저블입니다.
- 자체적으로 건너뛸 수 있는 컴포저블만 호출하는 컴포저블입니다.
- 비용이 많이 드는 equals 구현이 있는 매개변수가 많은 컴포저블입니다. 이 경우 매개변수가 변경되었는지 확인하는 데 드는 비용이 저렴한 리컴포지션 비용보다 클 수 있습니다.
컴포저블을 건너뛸 수 있으면 작은 오버헤드가 추가되므로 그만한 가치가 없을 수 있습니다. 다시 시작할 수 있는 것이 그만한 가치가 있는 것보다 더 많은 오버헤드라고 판단되는 경우 다시 시작할 수 없도록 컴포저블에 주석을 추가할 수도 있습니다.