Develop/Kotlin

[Android] 너는 왜 inline Composable이야?

JunJangE 2024. 10. 22. 10:12

개요

Compose를 사용하다 보면, 어떤 Composable은 일반 함수로, 또 어떤 Composable은 inline 함수로 정의된 것을 확인할 수 있다.

예를 들어 Box 컴포저블도 다음과 같이 두 가지 형태로 제공된다.

@Composable
fun Box(modifier: Modifier) {
    Layout(measurePolicy = EmptyBoxMeasurePolicy, modifier = modifier)
}

@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
) {
    val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
    Layout(
        content = { BoxScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

이 외에도 Column과Row도 inline 함수이다.

왜 저 Box 컴포저블은 inline 함수일까?

inline을 사용하는 이유는 일반 함수에서와 동일하다. 자주 호출되는 함수거나 복잡한 람다식이 포함된 경우 성능 최적화를 위해 inline을 사용한다.

 

[Kotlin] 얼렁뚱땅 inline 탐험일지 🧐

탐험 개요고차함수를 활용하기 위해 컬렉션 함수 내부 코드를 보던 중 inline을 발견!inline이 뭐지 코드 줄을 안으로 뭐 하는건가???.. 일단 코틀린에서 제공하는 고차함수 API에서 쓰이는 것으로 확

fre2-dom.tistory.com

Box 컴포저블에서도 동일한 이유로 inline 키워드를 적용한다. 특히, 이 함수 내부에서 사용하는 BoxScope는 Box 내부에서만 동작하는 레이아웃 관련 API를 제공하는데, inline 키워드를 사용하면 BoxScope 내부에서 정의된 람다가 매번 객체로 생성되는 것을 방지할 수 있다. 이렇게 하면 함수 호출 비용메모리 사용량을 줄여 성능을 더욱 최적화할 수 있다.

💡 즉, 컴포저블에서 inline을 사용하는 이유는 성능 최적화뿐만 아니라, BoxScope와 같은 UI 스코프가 자주 호출되는 상황에서 불필요한 람다 객체 생성을 피하고, 코드가 효율적으로 실행되도록 보장하기 위함이다.

inline을 사용하기 적합한 컴포저블

// inline Comopsable
@Composable
inline fun CustomButton(
    modifier: Modifier = Modifier,
    shape: RoundedCornerShape = RoundedCornerShape(8.dp),
    buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
    buttonTitle: String,
    enabled: Boolean = true,
    crossinline onClick: () -> Unit,
) {
    Button(
        modifier = modifier.fillMaxWidth(),
        onClick = { onClick() },
        shape = shape,
        colors = buttonColors,
        enabled = enabled,
    ) {
        Text(
            text = buttonTitle,
            fontSize = 14.sp,
            fontWeight = FontWeight.W500,
            modifier = Modifier.padding(vertical = 15.dp),
        )
    }
}

// 복잡한 람다
val complexLambda: () -> Unit = {
    // 많은 로직이 포함된 경우
    for (i in 1..1000) {
        println("복잡복잡 $i")
    }
}

// Composable 호출
@Composable
fun ExampleUsage() {
 CustomButton(
        modifier = Modifier.padding(horizontal = 32.dp),
        shape = RoundedCornerShape(100.dp),
        buttonColors = ButtonDefaults.buttonColors(containerColor = Blue50),
        buttonTitle = stringResource(R.string.sign_up_button),
        onClick = complexLambda,
    )
}

위와 같은 예시에서 inline 키워드는 버튼 클릭 처리에 사용되는 람다 객체 생성을 방지하며, 성능을 최적화할 수 있다.

💡 컴포저블 함수가 자주 호출되거나 복잡한 람다식을 포함할 때 이를 적절히 사용함으로써 성능과 메모리 효율성을 높일 수 있다.

crossinline을 아주 간단하게 알아보면...

crossinline은 inline 함수의 매개변수로 사용되는 람다 표현식에서 사용할 수 있는 키워드이다. 이 키워드를 사용하면 해당 람다에서 return 문을 사용할 수 없게 된다. (비지역 반환(Non-Local Return)이라고도 한다) 이는 inline 함수가 중첩된 함수에서 반환될 때, 호출 스택에 영향을 미쳐 예기치 않은 동작을 방지하기 위한 것이다.

💡 즉, crossinline을 사용하면 해당 람다가 다른 함수의 반환과 상관없이 독립적으로 실행된다.