[Compose] Recomposition

devel_liz·2024년 11월 27일
1

Compose

목록 보기
19/20


Recomposition이란?

  • Recomposition은 Jetpack Compose에서 UI를 다시 그리는 과정입니다.

  • 화면에 보여주는 데이터가 변경되면 해당 데이터를 사용하는 Composable 함수만 자동으로 다시 호출되어 변경된 내용을 반영합니다.

  • 쉽게 말해, 데이터가 바뀌면 필요한 부분의 UI만 업데이트된다.
    이로 인해 성능이 좋아지고, UI 갱신 로직을 따로 작성하지 않아도 된다.


Composable(컴포저블)의 수명 주기


컴포지션 내 컴포저블의 수명 주기. 컴포저블은 컴포지션을 시작하고 0회 이상 재구성되고 컴포지션을 종료합니다.

  • 초기 컴포지션: UI를 그리기 위해 호출한 Composable을 추적
  • 리컴포지션: 상태가 바뀌었을 때 예약되며(0회 이상) 변경점을 반영하려 컴포지션을 업데이트함.(상태가 바뀌어야만 리컴포지션을 하기 때문에 컴포지션을 바꾸는 유일한 방법은 상태 변경이다.)
    • 0회인 경우 -> 리컴포지션이 일어나지 않은 경우
    • State<T>가 변경되면 리컴포지션이 트리거 됨
    • State<T>를 읽는 모든 컴포저블 및 호출하는 컴포저블 중 건너뛸 수 없는 모든 컴포저블을 실행
    • 모든 입력이 안정적이고 변경되지 않았으면 건너뛸 수 있음

컴포지션은 초기 컴포지션을 통해서만 생성되고 리컴포지션을 통해서만 업데이트될 수 있다. 컴포지션을 수정하는 유일한 방법은 리컴포지션을 통하는 것뿐이다.

그럼 컴포지션은 초기 컴포지션이랑 리컴포지션으로 나뉘고 컴포지션의 뜻은 컴포저블을 실행한다는 뜻이야?


맞아! 컴포지션컴포저블 함수를 실행하여 UI를 그리는 과정이야.

구체적으로 나누면

  1. 초기 컴포지션
  • 컴포저블 함수가 처음 실행될 때 발생해
  • UI를 처음으로 구성하며, 모든 컴포저블 함수가 호출돼
  • 초기 상태에서 컴포저블 함수의 내용에 따라 UI가 렌더링 돼
  1. 리컴포지션
  • 상태가 변경될 때, 해당 상태를 사용하는 컴포저블 함수만 다시 실행돼
  • 변경된 상태와 관련된 UI만 업데이트해. 즉, 전체 UI를 다시 그리는 거 ㄴㄴ 필요한 부분만 다시 그려!!

컴포지션의 본질은 "컴포저블을 실행한다"
앞에서 입이 닳도록 반복했지만 컴포지션은 컴포저블 함수를 실행하여 UI를 구성하는 작업이야. 컴포저블 함수는 상태와 데이터에 기반하여 "어떤 UI를 보여줄지" 선언하는 함수야. 컴포지션이 발생할 때마다 컴포저블 함수는 실행되고, Compose는 UI를 최신 상태로 유지해.


컴포저블 구분은 어떻게 해?

컴포지션 내 컴포저블의 인스턴스는 호출 사이트로 식별돼. Compose 컴파일러는 각 호출 사이트를 고유한 것으로 간주하거든.

호출 사이트? 너 누군데.

호출 사이트는 컴포저블이 호출되는 소스 코드 위치야. 호출 사이트는 컴포지션 내 위치와 UI 트리에 영향을 미치니까 잘 기억해 둬.


더 이해하기 쉽게 안드로이드 공식 문서에 나와 있는 코드와 다이어그램을 예시로 설명해줄게.

@Composable
fun LoginScreen(showError: Boolean) {
  if (showError) {
      LoginError()
  }
  LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

상태가 변경되고 리컴포지션이 발생할 때 컴포지션 내 LoginScreen의 표현. 색상이 동일하면 재구성되지 않았음을 의미합니다.

왼쪽, 오른쪽을 보면 LoginInput은 두 번 연달아 호출되었어. 그렇지만 고유한 호출 사이트로 리컴포지션 되지 않았어.

그럼 같은 컴포저블을 여러 번 호출하는 경우는 어떡하라고?

그 경우에 사용하는 게 바로 "스마트 리컴포지션"이야.
Compose가 각 컴포저블 호출을 고유하게 식별할 수 있는 정보가 없으므로 인스턴스를 구분하기 위해 호출 사이트 외에 실행 순서가 사용돼. 이 동작만 필요한 경우도 있지만 경우에 따라 원치 않는 동작이 발생할 수도 있으니 주의해서 사용해야 해.

설명만으로는 이해가 안 될 것 같아서 예시를 보여줄게!

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

for문을 통해 movies에 있는 movie를 다 털어낼 때까지 MovieOverview(movie)가 반복될 거야. 그럼 어찌되었든 같은 컴포저블이 여러번 반복해서 호출되겠지.

목록의 하단에 새 요소가 추가된 경우 되면 컴포지션 내 MoviesScreen의 표현. 컴포지션의 MovieOverview 컴포저블은 재사용할 수 있습니다. MovieOverview의 색상이 동일하면 컴포저블이 재구성되지 않았음을 의미합니다.

위의 예에서 Compose는 호출 사이트 외에 실행 순서를 사용하여 컴포지션에서 인스턴스를 구분했다고 치자. 새 movie가 목록의 하단에 추가된 경우 Compose는 인스턴스의 목록 내 위치가 변경되지 않았고 따라서 인스턴스의 movie 입력이 동일하므로 컴포지션에 이미 있는 인스턴스를 재사용할 수 있어.

하지만 목록의 상단 또는 가운데에 항목을 추가하거나 항목을 삭제하거나 재정렬하여 movies 목록이 변경되면 목록에서 입력 매개변수의 위치가 변경된 모든 MovieOverview 호출에서 리컴포지션이 발생해. 이는 예를 들어 MovieOverview가 부수 효과를 사용하여 영화 이미지를 가져오는 경우 매우 중요해. 효과가 적용되는 동안 리컴포지션이 발생하면 효과가 취소되고 다시 시작되거든.

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

목록에 새 요소가 추가될 때 컴포지션 내 MoviesScreen의 표현 MovieOverview 컴포저블은 재사용할 수 없으며 모든 부수 효과가 다시 시작됩니다. MovieOverview의 색상이 다르면 컴포저블이 재구성되었음을 의미합니다.

위 다이어그램처럼 다시 재정렬되는 걸 막기 위해선 각 movie에는 movies 사이에 고유한 key가 있어야 해.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

목록에 새 요소가 추가될 때 컴포지션 내 MoviesScreen의 표현 MovieOverview 컴포저블에는 고유 키가 있으므로 Compose가 변경되지 않은 MovieOverview 인스턴스를 인식하고 재사용할 수 있습니다. 인스턴스의 부수 효과는 계속 실행됩니다.

이처럼 고유한 key를 같이 전달해 준다면
목록의 요소가 변경되더라도 Compose는 개별 MovieOverview 호출을 인식하고 재사용할 수 있어

key 컴포저블을 사용하면 Compose가 컴포지션에서 컴포저블 인스턴스를 식별할 수 있고, 이 기능은 여러 컴포저블이 동일한 호출 사이트에서 호출되고 부수 효과 또는 내부 상태가 포함되어 있을 때 유용해


💡 Tip
일부 컴포저블에는 key 컴포저블 지원 기능이 내장되어 있습니다. 예를 들어 LazyColumn의 경우 items DSL에 맞춤 key를 지정할 수 있습니다.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
   LazyColumn {
       items(movies, key = { movie -> movie.id }) { movie ->
           MovieOverview(movie)
       }
   }
}

recomposition을 건너뛸 수도 있어?

모든 입력이 안정적이고 변경되지 않았으면 건너뛸 수 있어

  • 안정적인 타입이란?
    • 두 인스턴스 equals 결과가 영원히 같은 경우
    • 공개 프로퍼티가 변경되면 composition에 알려야 함
    • 모든 공개 프로퍼티는 안정적이어야 함
    • @Stable이 표기되지 않아도 Compose 컴파일러가 안정적이다고 간주하는 공통 타입들
      • 모든 프리미티브 타입: Boolean, Int, Long, Float 등
      • 문자열
      • 모든 함수 타입(람다)

* 안정적이다고 추론할 수 없는 경우 Compose가 스마트 재구성을 선호하도록 유형에 @Stable을 표기해야 함


랜더링 단계

  • Composition
    • Composable 함수 호출
      • 선언된 Composable 함수가 호출되며 UI 트리(구성 요소의 계층 구조)가 만들어집니다.
    • Recomposition 트리 생성
      • Compose는 이 과정에서 데이터를 기반으로 화면 구조를 구성하고, UI 요소의 위치 및 속성을 기록합니다.
  • Layout
    • 측정 및 배치
      • 생성된 UI 트리에서 각 Composable 요소의 크기와 위치를 결정합니다.
        • 부모 요소가 자식 요소를 측정하고 위치를 할당합니다.
        • 이를 통해 UI가 원하는 공간에 정확히 배치됩니다.
          • 예: Column 안의 Text와 Button이 어떻게 배치될지를 계산합니다.
  • Drawing
    • 화면 렌더링
      • 레이아웃 정보를 기반으로 실제 화면에 UI 요소를 그립니다.
        • Compose는 Skia 그래픽 엔진을 사용하여 빠르게 그립니다.
        • 최적화를 위해 변경된 부분만 다시 그립니다.

Recomposition과 Layout/Drawing의 관계

  • Recomposition은 필요한 UI만 다시 구성합니다.
  • 하지만 Layout과 Drawing은 Recomposition이 발생하지 않아도 UI의 크기나 위치가 바뀌는 경우 따로 수행될 수 있습니다.
  • 팁: Recomposition이 항상 Layout과 Drawing을 동반하는 것은 아닙니다.
    효율적인 렌더링을 위해 Compose는 최소한의 작업만 수행합니다.

전체 흐름 요약

  1. 데이터 변경 → Recomposition 발생
  2. Composable 함수가 새롭게 호출되어 UI 트리 갱신
  3. 변경된 UI를 기반으로 측정 및 배치 수행 (Layout)
  4. 새롭게 갱신된 화면만 다시 그리기 (Drawing)

📌마치며

XML과 Compose 비교할 때마다 "Compose는 선언적이고 변경된 부분만 리컴포지션 해줘서 성능이 좋잖아요. 그래서 Compose를 써요."라고 하고 다녔는데 이것도 틀린 이야기가 아니지만 동작이 어떻게 되는지 자세히 알고 있지 못한 상태에서 공식처럼 하는 대답이었다. Compose의 작동 방식을 샅샅이 들여다보니 어느 부분에서 성능이 좋다고 하는 건지 몸소 느끼게 되었다. 또한 Compose에 대해 더 흥미를 갖게 되어 유익한 시간이었다.

profile
Android zizon

2개의 댓글

comment-user-thumbnail
2024년 11월 28일

저 귀여운 햄찌 덕에 내용 집중이 어렵습니다

1개의 답글

관련 채용 정보