성장기록지
Compose Stability 자세히 알아보기! 본문
Smart Recomposition
이전 글에서 작성하였듯이, Compose 에서는 컴파일 시점에 객체들의 “안정성(Stability)“을 확인한다.
그리고 어떤 컴포저블 함수에 사용 된 모든 인자가 안정적이라면, 그 컴포저블 함수는 “생략 가능하다(Skippable)“라고 본다.
리컴포지션이 발생했을 때, 어떤 컴포저블 함수의 모든 인자가 안정적(Stable)이고 그 값이 전혀 변하지 않았다면 리컴포지션을 생략한다.
안정의 조건
Compose에서는 크게 3가지 기준을 통해 안정성을 판단한다.
- 두 인스턴스가 같은 상태라면 두 객체의 비교 결과(equals)는 항상 같아야 한다. (동등성)
- 객체가 가진 모든 public 필드는 안정된 상태여야 한다.
- 만약 값이 변경된다면 컴포저블에 알려져야 한다.
추가적으로 Compose 컴파일러에 의해서 기본적으로 안정적이라고 판단하는 것들이 있다.
- 원시 타입
- 문자열
- 함수 (람다)
값이 변경되었을 때 컴포저블에 알려져야 한다?
객체의 변경여부를 감시하거나 객체가 Composer에게 자기가 바뀌었다고 알려 주어야 가능하다.
Jetpack Compose에서는 State객체가 그 역할을 수행한다.
@Composable
fun Sample1() {
var message = ""
TextField(
value = message,
onValueChange = { str -> message = str }
)
}
@Composable
fun Sample2() {
var message by remember { mutableStateOf("") }
TextField(
value = message,
onValueChange = { str -> message = str }
)
}
예시 코드를 봐보자, sample1의 message는 그냥 가변 String을 넣어주었고,
Samplle2의 message는 mutableState로 관리되어진다.
sample1의 message는 onValueChange 를 통해 값은 변하지만 compose는 값이 바뀐지 알 수 없다.
하지만 sample2에서 state는 값이 변하면 그 composable 함수에 그 사실을 알리는 value holder이기 떄문에,
값의 변경 시 recomposition을 수행 한다. 자세한건 이전에 작성한 state 글을 참고하자.
함수 (람다)
함수(람다)는 기본적으로 안정된 객체로 취급한다.
아래와 같은 람다를 봐보자..
val count: Int = 4
...
val lambda2 = { println("count: $count") }
위의 람다는 람다 밖에 있는 count라는 변수를 참조한다. 이렇게 람다 밖의 값을 참조하는 것을 캡쳐링(Capturing) 이라고 한다.
안정적인 외부 값을 캡쳐하는예시코드 같은 경우 캡쳐하는 값과 람다를 remember로 감싸 최적화한다.
remember의 동작에 따라서 캡쳐한 값이 변경되었을 때에만 새 람다 객체를 생성한다.
문제는 불안정한 값을 캡쳐하는 람다에 있다. 캡쳐링하는 외부 값이 불안정하다면 remember 최적화가 적용되지 않는다. 즉, 불안정한 값을 캡쳐링하는 람다는 매 리컴포지션마다 새 인스턴스를 만드는 방식으로 동작한다.
가능하면 람다에서 불안정한 것을 캡쳐하는 일을 찾아서 줄이는 게 좋다. 예시를 들자면 ViewModel이 있다. 람다 내에서 ViewModel의 프로퍼티나 함수 등을 사용하는 경우 그 람다는 ViewModel을 캡쳐한다. ViewModel은 거의 대부분 불안정한 상태이기 때문에 람다는 remember의 도움을 받지 못하는 방향으로 컴파일된다. 이를 해결하려면 람다가 안정적인 값만 캡쳐하도록 수정하거나 캡쳐하는 값을 안정화 시켜주어야 한다.
NonRestartableComposable
@NonRestartableComposable은 Compose 컴파일러에게 Composable 함수가 호출 매개변수의 변경으로 인한 recomposition 프로세스 중에 자동으로 다시 시작되지 않아야 함을 알림
이 어노테이션을 적용하면 함수를 다시 시작하지 않고 매개변수를 업데이트하도록 지시하여 내부 상태와 진행 중인 사이드 이펙트를 유지할 수 있음
대표적인 예는 LaunchedEffect이다. LaunchedEffect에서 이 어노테이션을 사용하여 불필요하게 이펙트가 다시 시작되지 않도록 한다.
불필요한 재시작을 방지함으로써 사이드 이펙트나 recomposition을 통해 지속되어야 하는 상태와 관련된 경우에 효율성과 일관성을 유지하는데 도움이 된다. 그러나 @NonRestartableComposable 어노테이션을 무분별하게 사용해서는 안된다.
참고 자료
https://cheonjaeung.com/posts/jetpack-compose-recomposition-and-stability-system/#fn:1
https://developer.android.com/develop/ui/compose/lifecycle#skipping
'안드로이드 > 안드로이드 지식' 카테고리의 다른 글
Kotlin Data Stream (Sequence, Hot, Cold Stream) (0) | 2025.01.23 |
---|---|
Compose Structure 정리하기 (compiler, Runtime, UI) (0) | 2025.01.22 |
Compose Stability란? (0) | 2025.01.20 |
안드로이드 MVVM 아키텍쳐란? (0) | 2025.01.13 |
data binding이란? (0) | 2025.01.11 |