attached to post

TIL - 14 # Compose @Immutable, @Stable Jetpack Compose를 사용하다 보면 “@Immutable” 과 “@Stable” 어노테이션을 본적이 있으실 겁니다. 해당 어노테이션은 Compose에서 퍼포먼스적으로 향상시킬수 있는 중요한 역할을 합니다. 해당 어노테이션들이 정확하게 어떠한 역할을 하는지 알아보겠습니다. ## Recomposition 어노테이션들을 알기 전에 “Recomposition” 즉 재구성이 어떻게 진행되는지 알고 가야 합니다. 공식 문서상에서는 이와 같이 설명되어 있습니다. > 재구성(Recomposition)은 입력이 변경될 때 구성 가능한 (composable)한 함수를 다시 호출하는 프로세스입니다. 이러한 것들은 함수의 입력값이 변경 되었을 때 발생을 하며, Compose가 새로운 입력값에 의해서 재구성이 되었을때, `변경 되었을지도 모르는` 함수 또는 람다들만 호출합니다. 그리고 나머지는 건너뜁니다. 변경되지 않는 함수 또는 람다에서 파라미터의 변경이 없는 것들을 건너뜀으로써 효과적으로 Compose는 재구성을 하게됩니다. > 간단하게 정리를 하자면, 변경되어서 영향이 있는 함수 또는 람다만 재구성을 하고 나머지는 건너뛰기를 하여서 재구성하는데 효율성을 높일 수 있다는 겁니다. 여기서 “변경 되었을지도 모르는” 이라는 것에 주의해야 합니다. Compose에서는 캄포저블의 매개변수가 업데이트 되지 않았음을 확신할 수 있는 경우에만 컴포저블을 건너뛴다는 것입니다. ## 업데이트 되지 않았음을 확신할 수 있는? 업데이트되지 않았음을 확신할 수 있다는 것이 어떤 것을 의미하는 걸까요? 아래와 같은 코드가 있다고 해보겠습니다. ```kotlin @Composable fun ShowSomething( modifier: Modifier = Modifier, title: String ) { } ``` 여기서 보면 modifier은 불변 객체이고, title도 String으로 불변 객체라고 할 수 있습니다. 따라서 Compose 컴파일러에서는 아래와 같이 해석을 합니다. ```kotlin restartable scheme("[androidx.compose.ui.UiComposable]") fun SomeClass2( stable modifier: Modifier? = @static Companion stable title: String ) ``` 여기서 주의 깊게 봐야 하는 것은 “stable” 입니다. 컴파일러에서는 “stable” 또는 “unstable”이라고 표시합니다. 여기서 “stable” 안정상태는 아래와 같은 조건을 갖추어야 합니다. - Compose에 값이 변경되었다는 것을 알릴 수 있어야 합니다. - 모든 공개 필드 들이 안정 상태여야 합니다. - 불변이어야 합니다. 이 경우에는 입력값에 변경이 없지만, 재구성을 해야될때 전체가 stable이기 때문에 전체 재구성이 일어나지 않습니다. 하지만 아래 같은 경우는 어떨까요? ```kotlin @Composable fun ShowSomething( modifier: Modifier = Modifier, title: String, list: List<User> ) { } ``` 아까와 다른 부분은 파라미터에 List가 추가되었습니다! 언뜻 보면 List가 인터페이스고 불변 객체로 보입니다. Compose 컴파일러로 확인해 볼까요? ```kotlin restartable scheme("[androidx.compose.ui.UiComposable]") fun SomeClass2( stable modifier: Modifier? = @static Companion stable title: String unstable list: List<User> ) ``` 확인해 보니 “unstable”이라는 것이 추가된것을 확인할 수 있습니다. List는 불변 객체로 보이는데 왜 “unstable”까요? 왜냐하면, “mutableList”와 같이 안에 내용물을 변경할 수 있는 collection이기 떄문입니다. 따라서 컴파일러는 “unstable”로 표시를 하며, 입력값에 변경이 없지만, 재구성을 해야될때 전체적으로 재구성을 하게 됩니다. 그래서 전체적으로 stable을 하기 위해서 최적화를 해야합니다. 최적화를 하기 위해서 3가지 방법이 있습니다. “@Stable”, “@Immutable”, “Immutable Collections” 입니다. 컴파일러에 대해 상세한 부분은 아래 링크에서 확인하실 수 있습니다. https://github.com/androidx/androidx/blob/08c6116/compose/compiler/design/compiler-metrics.md ## @Stable “@Stable”은 값이 변경될 때 컴파일러에 변경된다고 알려 준다고 표시하는 겁니다. “mutableStateOf()”를 사용해서 알려줄 수 있습니다. ```kotlin @Stable class SomeClass { var flag by mutableStateOf(false) } ``` ## @Immutable “@Immutable”을 사용하면, Composable에 값을 넘길 때 항상 새로운 복제한 데이터만 넘긴다고 선언하는 것입니다. 즉 객체가 불변하다고 약속하는 겁니다. ```kotlin @Immutable data class SomeClass( list: List<User> ) @Composable fun ShowSomething(data: SomeClass) { } ``` 여기서 주의해야 하는 점은 데이터를 지우거나 추가할 때 새로운 객체를 생성, 새로운 리스트를 생성한 후 추가를 해야 한다는 점입니다. ## Immutable CollectionsImmutableSet” 또는 “ImmutableList” 라는 Collection 을 사용하면, 컴파일러에서 stable하다고 인식할 수 있습니다. ## 결론 잘못된 최적화는 생각지도 못한 결과로 나타낼 수 있습니다. 최적화가 필요한 부분에서 적절히 판단하여 “@Stable”, “@Immutable”, “Immutable Collections”을 사용하면 될 것 같습니다. 참고 - https://stackoverflow.com/questions/68575936/what-do-the-stable-and-immutable-annotations-mean-in-jetpack-compose - https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8 - https://developer.android.com/jetpack/compose/performance/stability

콘텐츠를 더 읽고 싶다면?
원티드에 가입해 주세요.
로그인 후 모든 글을 볼 수 있습니다.
댓글 1