Kotlin을 사용하다 보면 Inline 함수를 무심코 활용하게 됩니다. 혹시, "내가 언제 Inline 함수를 사용했지?"라는 생각이 드셨나요? 특히 foreach나 each와 같은 문맥에서 inline
함수를 흔히 접할 수 있습니다.
text.indices.forEach { c ->
}
이렇게 Kotlin을 사용하면서 자연스럽게 Inline 함수를 활용하는 자신을 발견하게 됩니다.
이번 시간에는 Kotlin에서 필수적인 역할을 하는 Inline 함수에 대해 알아보려고 합니다.
Inline 함수는 무엇일까?
"inline" 을 두 단어로 나누면 in + line 으로생나눌 수 있는데요. 문자를 풀이하면, 라인에 들어간다고 풀이 할 수 있습니다.
Inline은 함수지만 컴파일시에 inline 함수로 선언된 함수 호출하는 부에 코드가 직접 들어가게 됩니다.
예를 들어 8줄짜리 inline 함수 안의 코드 존재한다고 가정합시다. 그 8줄짜리 코드가 그대로 Inline 함수 호출한 부분에 복사되어 들어갑니다.
Expected performance impact from inlining is insignificant.
Inlining works best for functions with parameters of functional types
인라인으로 인한 예상 성능 영향은 미미합니다. 인라인은 함수의 매개변수가 함수형 타입인 경우에 가장 효과적으로 작동합니다.
Kotlin에서는 함수형 타입의 매개변수를 갖는 함수가 일반적인 함수보다 인라인화에 더 적합하다고 알려져 있습니다. 이러한 함수형 타입의 매개변수를 활용하는 함수를 Kotlin에서는 고차 함수라고 부릅니다.
이제 이 고차 함수를 활용하여 inline을 어떻게 선언하는지 살펴보도록 하겠습니다.
고차 함수를 활요한 Inline 함수
private inline fun <T> inlineHello(name: T, func: (String) -> Unit) {
func("$name 안녕~!")
}
람다 식은 컴파일 단계에서 파라미터 개수에 따라 FunctionN
형태의 인터페이스로 변환됩니다. 따라서 람다를 사용한 고차 함수를 호출할 때 런타임에 인터페이스가 FunctionN
으로 생성되며, 이후 함수 호출이 진행됩니다.
그러나 이러한 접근 방식은 고차 함수를 호출할 때마다 내부적으로 객체를 생성하고 함수 호출을 수행하므로 성능 저하의 원인이 될 수 있습니다.
inline 키워드를 사용하여 함수를 인라인화하면 해당 함수 호출 부분의 변환된 바이트 코드가 직접 호출한 곳으로 들어가게 됩니다. 이로 인해 함수 호출 부분에서 람다 표현식을 FunctionN
인터페이스로 구현하는 객체를 생성하는 오버헤드가 발생하지 않습니다. 이를 통해 람다를 사용하는 고차 함수의 성능이 향상되고, 함수 호출에 따른 객체 생성 및 메모리 사용량이 줄어들게 됩니다.
이렇게 인라인 함수를 사용하면 런타임에서의 성능 개선과 메모리 관리 측면에서 이점을 얻을 수 있습니다.
Inline 함수 사용시 주의사항
inline 함수 내에서 함수 타입의 파라미터를 다른 함수의 파라미터로 전달하려고 할 때, 컴파일 에러가 발생하는 경우가 있을 수 있습니다. 이는 inline 함수 내에서 함수 타입 파라미터를 처리할 때 발생하는 제약 사항 때문입니다.
일반적으로 inline 함수는 함수 호출 시 해당 함수의 본문이 호출한 곳으로 복사되기 때문에 람다 표현식이나 함수 타입 파라미터가 inline 함수 내에서 사용될 때 몇 가지 제약 사항이 존재합니다. 특히, 해당 함수 타입 파라미터가 다른 함수 내에서 사용되는 경우 inline 되어 버리는데, 이렇게 되면 예기치 않은 결과나 컴파일 에러가 발생할 수 있습니다.
이러한 문제를 해결하려면 파라미터로 받은 함수 타입 파라미터에 noinline
키워드를 사용하여 해당 함수가 inline 되지 않도록 막을 수 있습니다. 예를 들어, 다음과 같이 noinline
키워드를 사용하여 해결할 수 있습니다.
private inline fun <T> inlineHello(name: T, noinline func: (String) -> Unit){
getHello(name, func)
}
private fun <T> getHello(name: T, func: (String) -> Unit) {
func("$name 반가워요~ :)")
}
번외) reified Fied 에 대해서
제네릭 타입을 사용하여 런타임 시에 그 타입의 실제 유형을 확인하거나 타입 캐스팅을 수행하는 경우가 있습니다.
이때 reified
키워드는 매우 유용합니다. 이 키워드는 Kotlin 언어에서 활용되며, 주로 inline
함수와 함께 사용됩니다.
reified
를 이용하면 제네릭 타입의 정보를 유지한 채로 런타임에서 해당 타입을 확인하거나 캐스팅할 수 있습니다.
inline fun <reified T> checkType(value: T): String? {
when (T::class) {
Int::class -> { // Type Int}
Float::class -> { // Type Float }
Double::class -> { // Type Double }
Long::class -> { // Type Long }
String::class -> { // Type String }
}
return null
}
마무리
Kotlin 언어의 inline
함수에 대한 학습 시간을 가졌습니다. inline 함수를 사용할 때 함수 매개변수의 유형에 따라서는 인라인화가 더 효과적일 수 있다는 사실을 이해하였습니다. 이제 이러한 개념을 실전에 적용하여 적절한 상황에서 inline
함수를 활용하는 것이 중요합니다.
참고