⏳ 수명 주기 개요
Composable 함수는 UI를 화면에 렌더링 하기 위해 여러 번 호출될 수 있습니다.
이 호출들은 각기 다른 수명 주기를 가지며, 이 주기들을 이해하면 성능을 최적화하고 예기치 않은 버그를 피할 수 있습니다.
💡 주요 수명 주기 단계
Initial Composition: 처음 UI가 컴포지션될 때, Composable 함수가 호출됩니다.
Recomposition: 상태 변화 등으로 인해 UI가 갱신되어야 할 때, 해당 Composable 함수가 다시 호출됩니다.
Skipping Recomposition: 입력이 변경되지 않으면 리컴포지션이 건너뛰어집니다.
Disposal: UI가 더 이상 필요하지 않을 때, 자원이 해제됩니다.
🔍 컴포지션 내 컴포저블의 분석
컴포지션 내 컴포저블의 인스턴스는 호출 사이트(컴포저블이 호출되는 소스 코드 위치)로 식별됩니다.
Compose 컴파일러는 각 호출 사이트를 고유한 것으로 간주합니다.
여러 호출 사이트에서 컴포저블을 호출하면 컴포지션에 컴포저블의 여러 인스턴스가 생성됩니다.
🧠 스마트 리컴포지션에 도움이 되는 정보 추가
Jetpack Compose는 스마트 리컴포지션을 통해, 변경된 부분만 효율적으로 갱신합니다.
이를 위해 Compose는 Composable 함수에 제공된 입력값들을 추적하고, 이 입력값들이 변경될 때만 해당 UI를 다시 렌더링 합니다.
🛠️ 최적화를 위한 팁
State Hoisting: 상태를 상위 컴포넌트로 올리면, 하위 Composable이 불필요하게 리컴포즈 되는 것을 막을 수 있습니다.
@Composable
fun ParentComposable() {
var counter by remember { mutableStateOf(0) }
Column {
Text(text = "Counter: $counter")
ChildComposable(onIncrement = { counter++ })
}
}
@Composable
fun ChildComposable(onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("Increment")
}
}
이 예시에서는 counter 상태를 ParentComposable에서 관리하고, 이를 ChildComposable로 전달합니다. ChildComposable 내에서 counter 상태가 변경되면, ParentComposable이 리컴포지션되지만, ChildComposable 자체는 리컴포지션되지 않습니다. 이를 통해 불필요한 리컴포지션을 줄일 수 있습니다.
Key 사용: key를 사용해 어떤 요소들이 리컴포지션의 대상이 되어야 하는지 명확히 할 수 있습니다.
@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
LazyColumn {
items(movies, key = { movie -> movie.id }) { movie ->
MovieOverview(movie)
}
}
}
LazyColumn에서 items를 렌더링 할 때 key를 사용하여 각 항목을 고유하게 식별합니다.
이 key는 Compose에게 어떤 항목이 변경되었는지, 어떤 항목이 리컴포지션되어야 하는지 알려줍니다.
만약 key를 사용하지 않으면, Compose는 각 항목의 위치에 따라 리컴포지션을 결정하게 되어, 불필요한 리컴포지션이 발생할 수 있습니다.
@Composable
fun MoviesScreen(movies: List<Movie>) {
Column {
for (movie in movies) {
key(movie.id) { // Unique ID for this movie
MovieOverview(movie)
}
}
}
}
이 코드에서는 MoviesScreen Composable이 movies 리스트를 받아 각 Movie 객체를 MovieOverview Composable로 렌더링 하는데, key(movie.id)를 사용해 각 영화의 고유 ID를 기반으로 리컴포지션을 최적화합니다.
key를 지정함으로써 Compose는 리스트 내 특정 영화가 이동하거나 변경될 때, 그 영화만 리컴포지션하고 나머지는 건너뛰게 되어 불필요한 UI 갱신을 방지하고 성능을 향상합니다.
⏩ 입력이 변경되지 않은 경우 건너뛰기
입력값이 변경되지 않으면, Jetpack Compose는 자동으로 리컴포지션을 건너뜁니다.
이로 인해 불필요한 연산이 줄어들고, 성능이 향상됩니다.
모든 입력이 안정적이고 변경되지 않은 경우, Compose는 해당 컴포저블의 리컴포지션을 건너뛸 수 있습니다.
이 비교는 equals 메서드를 사용합니다.
리컴포지션에서 건너뛸 수 없는 경우
일부 컴포저블은 특정 조건을 충족하지 않으면 리컴포지션을 건너뛸 수 없습니다.
아래의 경우를 포함합니다:
- 반환 유형이 Unit이 아닌 경우: Unit이 아닌 반환 유형이 있는 함수는 리컴포지션을 건너뛸 수 없습니다.
- 함수가 @NonRestartableComposable 또는 @NonSkippableComposable로 주석 처리된 경우: 이러한 함수들은 리컴포지션을 건너뛰지 않습니다.
- 필수 매개변수가 안정적이지 않은 유형일 경우: 매개변수 유형이 불안정하면 리컴포지션을 건너뛰지 않습니다.
Compose는 실험적인 컴파일러 모드인 Strong Skipping을 제공하며, 이 모드에서는 위의 마지막 조건이 완화됩니다.
안정적인 유형의 정의
유형이 안정적이라고 간주되려면 다음 조건을 충족해야 합니다:
- equals 결과의 일관성: 두 인스턴스의 equals 결과가 항상 동일해야 합니다.
- 속성 변경 알림: 유형의 공개 속성이 변경될 때 컴포지션에 알림이 전송되어야 합니다.
- 모든 공개 속성 유형이 안정적이어야 합니다.
이 계약을 충족하는 대표적인 일반 유형은 다음과 같습니다:
- 원시 값 유형: Boolean, Int, Long, Float, Char 등
- 문자열: String
- 함수 유형: 람다 함수
이들은 변경할 수 없기 때문에 안정적이라고 간주됩니다.
또한, Immutable(변경할 수 없는) 유형은 항상 안정적인 것으로 간주되므로 컴포지션에 변경사항을 알릴 필요가 없습니다.
MutableState의 안정성
Compose의 MutableState 유형은 안정적이지만 변경 가능한 대표적인 예입니다.
MutableState의. value 속성이 변경되면 Compose에 알림이 전송되므로, 상태 객체 전체는 안정적인 것으로 간주됩니다.
@Stable 주석의 사용
Compose는 유형이 안정적이라고 증명할 수 있을 때만 해당 유형을 안정적이라고 간주합니다.
예를 들어, 인터페이스는 일반적으로 안정적이지 않은 것으로 간주되며, 변경할 수 있는 공개 속성이 있는 유형도 안정적이지 않습니다.
만약 Compose가 유형의 안정성을 추론할 수 없지만 해당 유형이 안정적이길 원한다면, @Stable 주석을 추가하여 이를 명시적으로 표시할 수 있습니다.
@Stable
interface UiState<T : Result<T>> {
val value: T?
val exception: Throwable?
val hasError: Boolean
get() = exception != null
}
위의 코드에서 UiState는 인터페이스이므로 Compose가 일반적으로 이 유형을 안정적이지 않은 것으로 간주할 수 있습니다. 그러나 @Stable 주석을 추가하면 Compose는 이 유형이 안정적임을 알게 되며, 스마트 리컴포지션을 선호하게 됩니다.
즉, 인터페이스가 매개변수 유형으로 사용될 때 Compose는 모든 구현을 안정적인 것으로 간주합니다.
스마트 리컴포지션을 구현하려면 입력 유형의 안정성을 보장하는 것이 중요합니다.
안정적인 유형을 사용하면 불필요한 리컴포지션을 줄이고 성능을 최적화할 수 있습니다.
Compose가 유형의 안정성을 추론할 수 없는 경우, @Stable 주석을 사용하여 명시적으로 안정성을 부여할 수 있습니다.
'[Android - Compose] > Compose' 카테고리의 다른 글
[Compose] LazyColumn Drag And Drop Reordering (0) | 2024.09.09 |
---|---|
[Compose] Jetpack Compose 상태 관리 기초 (0) | 2024.09.08 |
[Compose] Slot-based Layouts (0) | 2024.09.06 |
[Compose] BottomNavigation 사용방법 (0) | 2024.09.06 |
[Compose] LazyListScope (0) | 2024.09.06 |