[Compose] 3 Performance Optimizations

김민형·2022년 11월 14일
0

Self Study

목록 보기
2/2

들어가며

소개하는 코드에서 Jetpack Compose 에 대해 자세히 설명하지 않습니다.
이 글은 Youtube 'Philipp Lackner' 의 가이드 영상을 보고 작성하였습니다. 공부의 목적으로 작성하였으며 영상을 본 후 작성자가 해석한 대로 적었기 때문에 사실과 다를 수 도 있습니다! 원본 영상을 참고하세요!
영상 : I Bet You DON'T Know These 3 Performance Optimizations for your Jetpack Compose UI

세 가지 퍼포먼스 최적화

첫 번째 경우에 대한 예시입니다.

Box(
	modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
) {
	RgbSelector(
    	color = viewModel.color,
        onColorClick = {
        	viewModel.changeColor(it)
        }
    )
}

굉장히 자주 사용하는 기본적인 형태의 코드인데 Red, Green, Blue 세 버튼을 클릭하면 사각형의 색이 해당 색으로 변경되는 간단한 애플리케이션입니다.

하지만 위와 같이 코드를 작성할 경우 버튼을 하나 누를 때 Effect 를 받는 사각형과 해당 버튼을 제외하고도 나머지 버튼들 까지 Recompose 됩니다. 이 경우 만약 퍼포먼스가 중요한 애플리케이션에서 화면 구성자체가 복잡해지고 커지게 되면 퍼포먼스 저하가 발생하게 됩니다.

그 이유는 아래의 부분이 람다식으로 작성되었기 때문입니다. 아래의 코드를 컴파일 할 때 Kotlin 에서 람다식은 Anonymous Class 로 작성을 하게 되고, Jetpack Compose 는 그 것을 Unstable 한 코드로 취급하여 처리하기 때문입니다. (해당 Composable 을 파라미터에 의해 변경이 일어날 때만 Recompose 해야하는데 그 것을 확실하게 알 수 없기 때문인 것 같습니다)

onColorClick = {
	viewModel.changeColor(it)
}

아래의 방법이 항상 정답인 것은 아닙니다.

Box(
	modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
) {
	RgbSelector(
    	color = viewModel.color,
        onColorClick = viewModel::changeColor
    )
}

로 변경하게 되면 클릭을 한 버튼만 영향을 받고 나머지 버튼은 Recompose 되지 않습니다.

두 번째 경우에 대한 예시입니다.

data class ExternalUser(
	val id: String,
    val email: String,
    val username: String
)
@Composable
fun WelcomeView(
	user: ExternalUser
) {
	Text(text = "Welcome ${user.username}!")
}

위의 ExternalUser 클래스는 외부 라이브러리나 API 를 가져와서 External Module 로 사용하는 경우를 보여줍니다. 그리고 그 아래의 코드와 같이 해당 user 를 파라미터로 받아 username 을 출력해주는 간단한 Composable 함수를 작성합니다.

위의 경우에도 컴파일 시 Jetpack Compose 는 해당 ExternalUser 클래스를 Unstable 하다고 취급합니다.

따라서 내부 모듈 (WelcomeView 가 속해있는 모듈) 안에 Mapper Class 를 작성해주면 해결할 수 있습니다.

data class User(
	val id: String,
    val email: String,
    val username: String
)

fun ExternalUser.toUser(): User {
	return User(
    	id = id,
        email = email,
        username = username
    )
}

fun User.toExternalUser(): ExternalUser {
	return ExternalUser(
    	id = id,
        email = email,
        username = username
    )
}

마지막 세 번째 경우에 대한 예시입니다.

코드 없이 말로 간단하게 설명해보겠습니다.
화면에 앞 면으로 세 가지 포커 카드가 보여지고, Shuffle 버튼을 클릭하면 야바위 처럼 세 가지 카드의 위치가 랜덤하게 섞이는 애플리케이션이라고 가정하겠습니다.

CustomGrid 라는 Composable 을 아래와 같이 작성합니다.

CustomeGrid(
	...
) {
	...
    Row(
    	...
    ) {
    	// 1 ~ 3
    	RowCards.forEach { card ->
        	CardView(card = card)
    	}
    }
    ...
}

여기에서 중요한 점은 Shuffle 버튼을 클릭하면 세 가지 카드의 순서를 변경하는 것 뿐이지 Card 자체가 변경되지 않습니다. (Card 의 숫자, 모양 등)
따라서, 버튼을 누른다 하더라도 CardView 가 Recompose 될 필요가 없습니다. Row 의 배치만 변경되면 되는 것입니다.
LazyRow 를 사용하면 알아서 처리해주지만 그렇지 않은 상황이라고 가정한다면 아래와 같은 방법을 사용하면 CardView 는 Recompose 되지 않고, 순서만 재배치됩니다.

Row(
    	...
    ) {
    	// 1 ~ 3
    	RowCards.forEach { card ->
        	key(card.num) {
            	CardView(card = card)
            }
    	}
    }

이 처럼 코드를 작성하게 되면 CardView 는 key 값이 변경되었을 때만 Recompose 되게 됩니다.

profile
Stick To Nothing!

0개의 댓글