사진: Unsplash의Noah Buscher
안드로이드 스튜디오에서 코틀린으로 개발을 하다 보면, KAPT나 KSP 플러그인을 추가해야 하는 상황이 생깁니다. 무의식적으로 일단 하라고 하니까, 해왔긴 한데 이게 뭔지 제대로 알아본 적이 없는 거 같아 이번 게시글에서 공부해보고자 합니다.
먼저 어노테이션이란?
KAPT, KSP에 대해 이야기하기 전에 먼저 어노테이션에 대해 이해하고 있어야 합니다.
어노테이션의 한국뜻은 `주석`으로 우리가 코드에 대한 설명을 주석을 통해 붙이듯 코드에 부가적인 정보를 제공하는 역할을 합니다.
부가적인 정보는 메티데이터의 형태로 제공되며 컴파일러나 런타임 환경에서 해석될 수 있습니다.
어노테이션하면 가장 먼저 `@Override`가 떠오르실 것 같습니다. 이 어노테이션은 메서드가 부모 클래스의 메서드를 오버라이드하고 있음을 컴파일러에게 알려주는 가장 흔한 어노테이션입니다.
어노테이션 프로세서
이렇게 작성된 어노테이션들은 자바 컴파일러가 `어노테이션 프로세서`를 사용해서 해당 어노테이션에 정의된 동작을 실행하는 데 사용됩니다.
어노테이션 프로세서는 코드를 생성, 검증하는 등 다양한 작업을 자동화할 수 있습니다.
KAPT란?
KAPT는 Kotlin Annotation Processing Tool의 약자로 코틀린으로 작성된 어노테이션을 위한 어노테이션 프로세서입니다.
기존에 자바로 작성된 어노테이션들은 자바 컴파일러에 의해 실행되지만, 자바 컴파일러 혼자서는 코틀린으로 작성된 어노테이션까지는 처리할 수 없습니다.
그렇기 때문에 KAPT가 존재합니다. KAPT는 코틀린 코드를 자바 스텁으로 컴파일하여, 자바 컴파일러가 코틀린으로 작성된 어노테이션을 처리할 수 있게 합니다.
KAPT를 사용했던 경우
안드로이드 개발을 하면서 KAPT는 필수적으로 적용해야 하는 플러그인은 아닙니다. 하지만 코틀린 어노테이션을 사용해야 할 일이 있다면 추가해야 합니다.
우리가 가장 많이 사용하는 코틀린 어노테이션을 활용하는 라이브러리가 바로 Hilt와 Room입니다. Hilt는 `@Inject`와 같은 어노테이션을 통해 의존성을 주입하고, `@Room`은 `@Dao`와 같은 어노테이션을 통해 데이터베이스 작업을 편하게 합니다.
이 라이브러리의 가장 큰 장점은 어노테이션을 사용해서 개발자가 작성할 코드의 양을 줄이고 어노테이션 프로세서가 자동으로 코드를 작성한다는 것입니다.
이러한 작업을 진행하는 Hilt Compiler와 Room Compiler는 자바 컴파일러로 코틀린 어노테이션을 변환하여 사용해야 하기 때문에 Hilt와 Room을 사용할 때는 KAPT를 사용해야 했습니다.
그럼 커스텀 어노테이션은?
여기서 한 가지 짚고 넘어가면 좋은 것이 있습니다. 바로 커스텀 어노테이션에 대한 이야기입니다.
커스텀 어노테이션은 코틀린으로 작성된 어노테이션이고, 메타데이터로 정의되어 리플렉션을 통해 사용될 수 있습니다.
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LogExecutionTime
그렇지만, KAPT가 필요하지 않은 이유는 커스텀 어노테이션은 단순히 마커로 작용하거나 런타임에 리플렉션을 통해 처리되기에 어노테이션 프로세서가 필요하지 않기 때문입니다.
만약 코틀린에서 어노테이션을 분석해서 코드를 자동 생성하는 작업을 수행하려면 KAPT 필요합니다.
KSP란?
위에서 말했듯이 KAPT는 코틀린 코드를 자바 스텁으로 컴파일하여, 자바 컴파일러가 코틀린으로 작성된 어노테이션을 처리할 수 있게 합니다. 이러한 스텁을 생성하려면 KAPT가 코틀린 프로그램의 모든 기호를 확인해야 하고, 이러한 스텁 코드를 생성하는 비용은 매우 거대합니다.
이러한 문제를 해결하기 위해서 KSP가 나왔습니다. KSP는 Kotlin Symbol Processing의 약자로 코틀린 코드를 자바 코드로 변환하는 과정 없이 심볼을 바로 접근하고 분석할 수 있는 API를 제공합니다.
KSP의 작동 원리와 역할
KSP는 Kotlin 프로그램의 구조를 심볼 수준에서 다룹니다. 여기서 심볼 수준이란, 프로그램을 구성하는 기본 요소(클래스, 함수, 매개변수 등)를 의미합니다. 예를 들어:
- 접근 가능: 클래스, 클래스 멤버(속성, 메서드), 함수, 매개변수와 같은 코드 구조.
- 접근 불가능: if 블록, for 루프 등 실행 흐름 제어를 위한 구문.
즉, KSP는 코드의 실행 흐름이나 논리를 분석하는 것이 아니라, 코드의 구조를 분석 및 처리합니다.
KSP는 Kotlin의 KType(Kotlin Reflection의 일부)과 개념적으로 유사합니다. 이 API는 프로세서가 클래스 선언에서 특정 타입 인수를 가진 해당 타입으로, 그리고 그 반대로 이동할 수 있도록 합니다. 또한, 타입 인수를 대체하고, 변형(variances)을 지정하며, 스타 프로젝션(star projections)을 적용하고, 타입의 null 가능성을 표시할 수 있습니다.
다른 관점에서 KSP는 Kotlin 프로그램의 전처리기 프레임워크로 볼 수 있습니다. KSP 기반 플러그인을 심볼 프로세서(또는 간단히 프로세서)로 간주할 때, 컴파일 시 데이터 흐름은 다음 단계로 설명됩니다:
- 프로세서는 소스 프로그램과 리소스를 읽고 분석합니다.
- 프로세서는 코드나 기타 형식의 출력을 생성합니다.
- Kotlin 컴파일러는 소스 프로그램과 생성된 코드를 함께 컴파일합니다.
KAPT와 KSP의 가장 큰 차이
KSP의 주요 특징은 "컴파일 시점에서 동작하며, 소스 코드를 변경하거나 언어의 의미를 수정할 수 없다"는 것입니다.
KSP는 오직 읽기 전용으로 동작하여 자바 스텁을 생성하는 KAPT와는 큰 차이가 존재합니다.
마무리하며
KSP는 Kotlin 소스를 직접 처리하므로 변환 과정이 없어 더 빠르고 효율적입니다. 메모리 사용량도 적으며, 컴파일 타임 단축 효과과 매우 큽니다.
이런 장점 때문에 안드로이드는 KAPT를 KSP로 마이그레이션하는 것을 권장하고 있습니다.
다만, DataBinding이나 Dagger는 KSP를 지원하지 않기 때문에 각자의 프로젝트에 도입하려고 할 때에는 이러한 사항을 고려할 필요가 있습니다.
Reference
'Android > Kotlin' 카테고리의 다른 글
왜 SharedFlow의 emit()은 suspend 함수일까? (2) | 2025.01.01 |
---|---|
Kotlin에서의 함수 컬러링이란? suspend와 composable 함수 (1) | 2024.10.15 |
Immutable 그리고 Persistent Collections에 대해서 (1) | 2024.02.26 |
Ktor에 대해 알아보고 HTTP 통신 해보기 (1) | 2023.12.04 |
@Inject로도 @Provide할 수 있다 (0) | 2023.11.25 |